XML序列化是將對象的公共屬性和字段轉換為XML格式,以便存儲或傳輸的過程。反序列化則是從XML輸出中重新創建原始狀態的對象。XML序列化中最主要的類是XmlSerializer類。它的最重要的方法是Serialize和Deserialize方法,它位於System.Xml.Serialization命名空間。
一、XML序列化基本要點
在本節開始之前,首先來看一個最簡單的示例:
namespace 學習測試
{
class Program
{
static void Main(string[] args)
{
Person p = new Person(1, "劉備", 176);
string xmlString = "";
//xml序列化開始
using (MemoryStream ms = new MemoryStream())
{
Type t = p.GetType();
XmlSerializer xml = new XmlSerializer(t);
xml.Serialize(ms, p);
byte[] arr = ms.ToArray();
xmlString = Encoding.UTF8.GetString(arr, 0, arr.Length);
ms.Close();
}
Console.WriteLine(xmlString);
Console.ReadKey();
}
}
public class Person
{
//必須定義一個無參數構造函數,否則無法序列化(當然完全不寫構造函數也是可以序列化的,因為有個默認的無參構造函數)
public Person() { }
public Person(int id, string name, int age)
{
Id = id;
Name = name;
//Age = age;
}
public int Id { get; set; }
public string Name { get; set; }
//私有字段
private int Age { get; set; }
//只讀屬性
private int height;
public int Height { get { return height; } }
}
}
該代碼輸出如下:

從以上輸出,我們可以得出結論,必須要求無參構造函數,默認的也可以。但注意當默認的無參構造函數比覆蓋時,要補上一個無參構造函數。另外,私有屬性,只讀屬性是不能被序列化的。
更多的注意事項:
- 要序列化的類必須有默認的構造的構造函數,才能使用XmlSerializer序列化;
- 方法不能被序列化;
- 索引器、私有字段或只讀屬性(只讀集合屬性除外)不能被序列化;
- 需要序列化的類都必須有一個無參的構造函數
- 枚舉變量可序列化為字符串,無需用[XmlInclude]
- 導出非基本類型對象,都必須用[XmlInclude]事先聲明。該規則遞歸作用到子元素
- Attribute中的IsNullable參數若等於false,表示若元素為null則不顯示該元素。(針對值類型有效)
- 某些類就是無法XML序列化的(即使使用了[XmlInclude])
- IDictionary(如HashTable)
- 父類對象賦予子類對象值的情況
- 對象間循環引用
9.對於無法XML序列化的對象,可考慮
- 使用自定義xml序列化(實現IXmlSerializable接口)
- 實現IDictionary的類,可考慮(1)用其它集合類替代;(2)用類封裝之,並提供Add和this函數
- 某些類型需要先經過轉換,然后才能序列化為 XML。如XML序列化System.Drawing.Color,可先用ToArgb()將其轉換為整數
- 過於復雜的對象用xml序列化不便的話,可考慮用二進制序列化。
不想序列化時:
- 當不想序列化一個屬性時,使用[System.Xml.Serialization.XmlIgnore]標記,能用於屬性;
- [NonSerializable]應用於屬性無效,能用於類,結構體等
默認構造函數是必須的,因為反序列化本質上使用的是反射,需要默認構造函數來實例化類,如果去掉其中的默認構造函數,則編譯沒有問題,但運行就會報錯。
盡量不要將比較大的屬性放在默認構造函數初始化,那會導致在反序列化時對列表初始化兩次:默認構造函數中執行一次,反序列化時從XML文檔讀取再執行一次。
二、改變XML序列化的默認值
通常,在XML序列化的過程中,有很多東西是自動生成的,例如XML命名空間,編碼等等。
1、去除默認的命名空間與前綴:
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", ""); //第一個參數是前綴,第二個參數是命名空間
//然后在序列化的時候,指定自定義命名空間
xml.Serialize(ms, p, ns);
輸出對比:

當然,這個方法也可以用於生成你想要的自定義命名空間。
2、去除XML聲明:頂部的 <?xml version="1.0" encoding="utf-8"?>
public static string ObjectToXmlSerializer(Object Obj)
{
XmlWriterSettings settings = new XmlWriterSettings();
//去除xml聲明
settings.OmitXmlDeclaration = true;
settings.Encoding = Encoding.Default;
System.IO.MemoryStream mem = new MemoryStream();
using (XmlWriter writer = XmlWriter.Create(mem, settings))
{
//去除默認命名空間xmlns:xsd和xmlns:xsi
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
XmlSerializer formatter = new XmlSerializer(Obj.GetType());
formatter.Serialize(writer, Obj, ns);
}
return Encoding.Default.GetString(mem.ToArray());
}
輸出:

3、換行縮進
settings.Indent = true;
當XmlWriterSettings如此設置后,輸出的XML為:

4、指定縮進字符
settings.IndentChars = "--";
輸出如下:

XmlWriterSettings更多設置屬性如下:
| 成員 | 說明 |
| CloseOutput | 獲取或設置一個值,該值指示在調用 Close 方法時,XmlWriter 是否還應該關閉基礎流或 TextWriter。 |
| Encoding | 獲取或設置要使用的文本編碼的類型。 |
| Indent | 獲取或設置一個值,該值指示是否縮進元素。 |
| IndentChars | 獲取或設置縮進時要使用的字符串。 |
| NamespaceHandling | 獲取或設置一個值,該值指示在編寫 XML 內容時,XmlWriter 是否應移除重復的命名空間聲明。 的默認是輸出程序中出現的所有命名空間聲明。 |
| NewLineChars | 獲取或設置要用於分行符的字符串 |
| NewLineHandling | 獲取或設置一個值,該值指示是否將輸出中的分行符正常化。 |
| NewLineOnAttributes | 獲取或設置一個值,該值指示是否將屬性寫入新行。 |
| OmitXmlDeclaration | 獲取或設置一個值指示省略 XML 聲明。 |
| Encoding | 獲取或設置要使用的文本編碼的類型。 |
| Reset方法 | 重置以上屬性 |
http://msdn.microsoft.com/zh-cn/library/system.xml.xmlwritersettings(v=vs.110).aspx
三、實現序列化接口IXmlSerializable
實現IXmlSerializable接口之后,我們能夠自定義類序列化的方式。
該接口包含3個方法:
XmlSchema GetSchema(); void ReadXml(XmlReader reader); void WriteXml(XmlWriter writer);
簡單示例:
namespace 自定義序列化
{
class Program
{
static void Main(string[] args)
{
Person p = new Person();
p.Id = 1;
p.Name = "劉備";
string str = ObjectToXmlSerializer(p);
Console.WriteLine(str);
Person p1 = ObjectToXmlDESerializer<Person>(str);
Console.WriteLine("我的名字是:" + p1.Name);
Console.ReadKey();
}
//序列化Xml
public static string ObjectToXmlSerializer(Object Obj)
{
string XmlString = "";
XmlWriterSettings settings = new XmlWriterSettings();
//去除xml聲明
//settings.OmitXmlDeclaration = true;
settings.Indent = true;
settings.Encoding = Encoding.Default;
using (System.IO.MemoryStream mem = new MemoryStream())
{
using (XmlWriter writer = XmlWriter.Create(mem, settings))
{
//去除默認命名空間xmlns:xsd和xmlns:xsi
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
XmlSerializer formatter = new XmlSerializer(Obj.GetType());
formatter.Serialize(writer, Obj, ns);
}
XmlString = Encoding.Default.GetString(mem.ToArray());
}
return XmlString;
}
//反序列化
public static T ObjectToXmlDESerializer<T>(string str)where T : class
{
object obj;
using (System.IO.MemoryStream mem = new MemoryStream(Encoding.Default.GetBytes(str)))
{
using (XmlReader reader = XmlReader.Create(mem))
{
XmlSerializer formatter = new XmlSerializer(typeof(T));
obj = formatter.Deserialize(reader);
}
}
return obj as T;
}
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
public class PersonSerializer : IXmlSerializable
{
private Person p;
public int Id { get; set; }
public string Name { get; set; }
#region IXmlSerializable 成員
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
throw new NotImplementedException();
}
//如果這個方法默認則報:XML 文檔(2, 2)中有錯誤。
void IXmlSerializable.ReadXml(XmlReader reader)
{
reader.ReadElementString("Person");
}
void IXmlSerializable.WriteXml(XmlWriter writer)
{
writer.WriteElementString("Id", Id.ToString());
writer.WriteElementString("Name", Name);
}
#endregion
}
}
輸出如下:

我們都知道,接口是不支持序列化的。下面來做個有用的示例,實現IList<T>的序列化與反序列化:
namespace IList<T>的序列化與反序列化
{
class Program
{
static void Main(string[] args)
{
Woman w1 = new Woman() { Id = 1, Name = "貂蟬" };
Woman w2 = new Woman() { Id = 2, Name = "西施" };
List<Woman> ListWoman = new List<Woman>();
ListWoman.Add(w1);
ListWoman.Add(w2);
Person p = new Person();
p.Id = 1;
p.Name = "劉備";
p.ListWoman = ListWoman;
string str = ObjectToXmlSerializer(p);
Console.WriteLine(str);
Person p1 = ObjectToXmlDESerializer<Person>(str);
Console.WriteLine("我的名字是:" + p1.Name + "我的老婆有:");
foreach (Woman w in p1.ListWoman)
{
Console.WriteLine(w.Name);
}
Console.ReadKey();
}
//序列化Xml
public static string ObjectToXmlSerializer(Object Obj)
{
string XmlString = "";
XmlWriterSettings settings = new XmlWriterSettings();
//去除xml聲明
//settings.OmitXmlDeclaration = true;
settings.Indent = true;
settings.Encoding = Encoding.Default;
using (System.IO.MemoryStream mem = new MemoryStream())
{
using (XmlWriter writer = XmlWriter.Create(mem, settings))
{
//去除默認命名空間xmlns:xsd和xmlns:xsi
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
XmlSerializer formatter = new XmlSerializer(Obj.GetType());
formatter.Serialize(writer, Obj, ns);
}
XmlString = Encoding.Default.GetString(mem.ToArray());
}
return XmlString;
}
//反序列化
public static T ObjectToXmlDESerializer<T>(string str) where T : class
{
object obj;
using (System.IO.MemoryStream mem = new MemoryStream(Encoding.Default.GetBytes(str)))
{
using (XmlReader reader = XmlReader.Create(mem))
{
XmlSerializer formatter = new XmlSerializer(typeof(T));
obj = formatter.Deserialize(reader);
}
}
return obj as T;
}
}
public class Person : IXmlSerializable
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Woman> ListWoman { get; set; }
#region IXmlSerializable 成員
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
throw new NotImplementedException();
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
//一定要特別注意配對問題,否則很容易反序列化集合出現只能夠讀取第一個的情況
reader.ReadStartElement("Person");
Id = Convert.ToInt32(reader.ReadElementString("Id"));
Name = reader.ReadElementString("Name");
//我也不知道為什么,復雜類型只能夠另外定義一個,獲得值之后再給原來的賦值
List<Woman> ListWoman2 = new List<Woman>();
reader.ReadStartElement("ListWoman");
while (reader.IsStartElement("Woman"))
{
Woman w = new Woman();
reader.ReadStartElement("Woman");
w.Id = Convert.ToInt32(reader.ReadElementString("Id"));
w.Name = reader.ReadElementString("Name");
reader.ReadEndElement();
reader.MoveToContent();
ListWoman2.Add(w);
}
ListWoman = ListWoman2;
reader.ReadEndElement();
reader.ReadEndElement();
}
void IXmlSerializable.WriteXml(XmlWriter writer)
{
//這里是不需要WriteStart/End Person的
writer.WriteElementString("Id", Id.ToString());
writer.WriteElementString("Name", Name);
//有重載,想設置命名空間,只需在參數加上
writer.WriteStartElement("ListWoman");
foreach (Woman item in ListWoman)
{
PropertyInfo[] ProArr = item.GetType().GetProperties();
writer.WriteStartElement("Woman");
foreach (PropertyInfo p in ProArr)
{
writer.WriteElementString(p.Name, p.GetValue(item, null).ToString());
}
writer.WriteEndElement();
}
writer.WriteEndElement();
}
#endregion
}
public class Woman
{
public int Id { get; set; }
public string Name { get; set; }
}
}
輸出如下:

以上代碼是能夠直接用於序列化數組的,也就是IList<Person>的,下面在貼上兩個序列化與反序列化IList<T>的方法:
//序列化Xml
public static string ListToXmlSerializer<T>(IList<T> ListT)
{
XmlSerializer ser = new XmlSerializer(ListT.GetType());
System.IO.MemoryStream mem = new MemoryStream();
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = false;
settings.Encoding = Encoding.UTF8;
XmlWriter writer = XmlWriter.Create(mem, settings);
ser.Serialize(writer, ListT);
writer.Close();
string strtmp = Encoding.UTF8.GetString(mem.ToArray());
return strtmp;
}
//反序列化Xml
public static List<T> XmlToListSerializer<T>(Stream stream)
{
string XmlPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + @"\OutLine\" + typeof(T).Name + ".xml";
using (StreamReader sr = new StreamReader(stream, System.Text.Encoding.UTF8))
{
XmlSerializer ser = new XmlSerializer(typeof(List<T>));
var listsch = ser.Deserialize(sr);
List<T> reses = listsch as List<T>;
return reses;
}
}
下面給出一個序列化與反序列化通過反射的復雜對象的示例:
using System.Linq.Expressions;namespace 控制台___學習測試
{
class Program
{
static void Main(string[] args)
{
Woman w1 = new Woman() { Id = 1, Name = "貂蟬" };
Woman w2 = new Woman() { Id = 2, Name = "西施" };
List<Woman> ListWoman1 = new List<Woman>();
ListWoman1.Add(w1);
ListWoman1.Add(w2);
List<Person> ListPerson = new List<Person>();
Person p1 = new Person() { Id = 1, Name = "劉備", ListWoman = ListWoman1 };
Person p2 = new Person() { Id = 2, Name = "關羽", ListWoman = ListWoman1 };
Person p3 = new Person() { Id = 3, Name = "張飛", ListWoman = ListWoman1 };
ListPerson.Add(p1);
ListPerson.Add(p2);
ListPerson.Add(p3);
string xml = ListToXmlSerializer(ListPerson);
Console.WriteLine(xml);
MemoryStream mem = new MemoryStream(Encoding.UTF8.GetBytes(xml));
List<Person> ListPerson2 = XmlToListSerializer<Person>(mem);
Console.WriteLine(ListPerson2.Count);
Console.WriteLine(ListPerson2[2].ListWoman[1].Name);
Console.ReadKey();
}
//序列化Xml
public static string ListToXmlSerializer<T>(IList<T> ListT)
{
XmlSerializer ser = new XmlSerializer(ListT.GetType());
System.IO.MemoryStream mem = new MemoryStream();
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
settings.Encoding = Encoding.UTF8;
XmlWriter writer = XmlWriter.Create(mem, settings);
ser.Serialize(writer, ListT);
writer.Close();
string strtmp = Encoding.UTF8.GetString(mem.ToArray());
File.WriteAllText(@"D:\222.xml", strtmp);
return strtmp;
}
//反序列化Xml
public static List<T> XmlToListSerializer<T>(Stream stream)
{
using (StreamReader sr = new StreamReader(stream, System.Text.Encoding.UTF8))
{
XmlSerializer ser = new XmlSerializer(typeof(List<T>));
var listsch = ser.Deserialize(sr);
List<T> reses = listsch as List<T>;
return reses;
}
}
}
public class Person : IXmlSerializable
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Woman> ListWoman { get; set; }
#region IXmlSerializable 成員
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
throw new NotImplementedException();
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
//while (reader.Name == "Person")
//{
reader.ReadStartElement("Person");
Id = Convert.ToInt32(reader.ReadElementString("Id"));
Name = reader.ReadElementString("Name");
List<Woman> newWomans = new List<Woman>();
PropertyInfo[] ProArr = typeof(Woman).GetProperties();
reader.ReadStartElement("ListWoman");
while (reader.IsStartElement("Woman"))
{
Woman Item2 = new Woman();
reader.ReadStartElement("Woman");
foreach (PropertyInfo p in ProArr)
{
string str = reader.ReadElementString(p.Name);
p.SetValue(Item2, Convert.ChangeType(str, p.PropertyType), null);
}
reader.ReadEndElement();
reader.MoveToContent();
newWomans.Add(Item2);
}
ListWoman = newWomans;
reader.ReadEndElement();
reader.ReadEndElement();
}
void IXmlSerializable.WriteXml(XmlWriter writer)
{
writer.WriteElementString("Id", Id.ToString());
writer.WriteElementString("Name", Name);
writer.WriteStartElement("ListWoman");
foreach (Woman item in ListWoman)
{
PropertyInfo[] ProArr = item.GetType().GetProperties();
writer.WriteStartElement("Woman");
foreach (PropertyInfo p in ProArr)
{
if (p.GetValue(item, null) != null)
{
writer.WriteElementString(p.Name, p.GetValue(item, null).ToString());
}
else
{
writer.WriteElementString(p.Name, "");
}
}
writer.WriteEndElement();
}
writer.WriteEndElement();
}
#endregion
}
public class Woman
{
public int Id { get; set; }
public string Name { get; set; }
}
}
以上代碼輸出:

特別提示,一定要特別特別注意,ReadStartElement與ReadEndElement的問題,否則很容易出現反序列化集合時只能夠讀取第一個的情況。而對於序列化,如果WriteStartElement與WriteEndElement不匹配,出現的只是XML標簽對不匹配的問題,沒Read的時候那么坑。
四、XML特性
有時,我們在序列化時想要自定義XML的結構,這時候就要用到我們的屬性類了。屬性類提供了很多特性供我們使用,以完成自定義序列化功能。
| 名稱 | 描述 |
| XmlAttribute | 表示一個特性對象的集合,這些對象控制XmlSerializer如何序列化和反序列化對象 |
| XmlArrayAttribute | 指定XmlSerializer應將特定的類成員序列化為XML元素數組 |
| XmlArrayItemAttribute | 指定XmlSerializer可以放置在序列化數組中的派生類型 |
| XmlArrayItemAttributes | 表示XmlArrayItemAttribute對象的集合 |
| XmlAttributeAttribute | 指定XmlSerializer應將類成員作為XML特性序列化 |
| XmlChoiceIdentifierAttribute | 指定可以通過使用枚舉來進一步消除成員的歧義 |
| XmlElementAttribute | 在XmlSerializer序列化或反序列化包含對象時,指示公共字段或屬性表示XML元素 |
| XmlElementAttributes | 表示XmlElementAttribute的集合,XmlSerializer將其用於它重寫序列化類的默認方式 |
| XmlEnumAttribute | 控制XmlSerializer如何序列化枚舉成員 |
| XmlIgnoreAttribute | 指示XmlSerializer方法不序列化公共字段或公共讀/寫屬性值 |
| XmlIncludeAttribute | 允許XmlSerializer在它序列化或反序列化對象時識別類型 |
| XmlRootAttribute | 控制視為XML根元素的屬性目標的XML序列化 |
| XmlTextAttribute | 當序列化或反序列化時,想XmlSerializer指示應將此成員作為XML文本處理 |
| XmlTypeAttribute | 控制當屬性目標由XmlSerializer序列化時生成的XML結構 |
| XmlAnyAttributeAttribute | 指定成員(返回XmlAttribute對象的數組的字段)可以包含XML屬性 |
| XmlAnyElementAttribute | 指定成員可以包含對象,該對象表示在序列化或反序列化的對象中沒有相應成員的所有XML元素 |
| XmlAnyElementAttributes | 表示XmlAnyElementAttribute對象的集合 |
| XmlAttributeEventArgs | 為UnKnowAttribute提供數據 |
| XmlAttributeOverrides | 允許你在使用XmlSerializer序列化或反序列化時重寫屬性、字段和類特性 |
| XmlElementEventArgs | 為UnknownElement事件提供數據 |
| XmlNamespaceDeclarationsAttribute | 指定目標屬性、參數、返回值或類成員包含與XML文檔中所用命名空間關聯的前綴 |
| XmlNodeEventArgs | 為UnknownNode時間提供數據 |
| XmlSerializer | 將對象序列化到XML文檔中和從XML文檔中反序列化對象,XmlSerializer使你得以控制如何將對象編碼到XML中 |
| XmlSerializerNamespaces | 包含XmlSerializer用於在XML文檔實例中生成限定名的XML命名空間和前綴 |
| XmlTypeMapping | 包含從一種類型到另一種類型的映射 |
更多更詳細的說明,可以在這里看到:http://msdn.microsoft.com/zh-cn/library/System.Xml.Serialization(v=vs.110).aspx
下面僅僅給出兩個簡單示例:
namespace 學習測試
{
[Serializable]
public class Person
{
public Person() { }
public int Id { get; set; }
public string Name { get; set; }
[XmlAttribute(DataType = "string")]
public string Content { get; set; }
[XmlIgnore]
public int Age { get; set; }
[XmlArray]
[XmlArrayItem("Int32", typeof(Int32))]
public IList ListInt { get; set; }
}
class Program
{
static void Main(string[] args)
{
IList list = new ArrayList();
list.Add(1);
list.Add(2);
list.Add(3);
Person p = new Person();
p.Id = 1;
p.Name = "劉備";
p.Age = 23;
p.Content = "這是一個牛人";
p.ListInt = list;
string strXml = ObjectToXmlSerializer(p);
Console.WriteLine(strXml);
//反序列化IList還有問題
//Person p2 = ObjectToXmlDESerializer<Person>(strXml);
//Console.WriteLine(p2.Name);
Console.ReadKey();
}
//序列化
public static string ObjectToXmlSerializer(Object Obj)
{
string XmlString = "";
XmlWriterSettings settings = new XmlWriterSettings();
//去除xml聲明
//settings.OmitXmlDeclaration = true;
settings.Indent = true;
settings.Encoding = Encoding.Default;
using (System.IO.MemoryStream mem = new MemoryStream())
{
using (XmlWriter writer = XmlWriter.Create(mem, settings))
{
//去除默認命名空間xmlns:xsd和xmlns:xsi
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
XmlSerializer formatter = new XmlSerializer(Obj.GetType());
formatter.Serialize(writer, Obj, ns);
}
XmlString = Encoding.Default.GetString(mem.ToArray());
}
return XmlString;
}
//反序列化Xml
public static T ObjectToXmlDESerializer<T>(string str) where T : class
{
object obj;
using (System.IO.MemoryStream mem = new MemoryStream(Encoding.Default.GetBytes(str)))
{
using (XmlReader reader = XmlReader.Create(mem))
{
XmlSerializer formatter = new XmlSerializer(typeof(T));
obj = formatter.Deserialize(reader);
}
}
return obj as T;
}
}
}
2013/12/27 常遇錯誤記錄:
反序列化錯誤提示:
1、XML 文檔(2, 2)中有錯誤:
報這個錯誤一般是由於序列化與反序列化的類型不一致:
XmlSerialize.Serialize(@"C:\Person.xml",person); //person 是 Person類的對象 var test = XmlSerialize.DeSerialize(typeof(Person), @"C:\Person.xml");
2014/08/12
2、XmlIgnore與NonSerialized的區別。
1、XmlIgnore能作用於屬性,NonSerialized只作用於字段。
2、XmlIgnore對序列化與反序列化均有效,而NonSerialized只影響序列化,反序列化不管。(非百分百確定)

