【題外話】
以前雖然常用.NET中的序列化,但是常用的BinaryFormatter,也就是二進制文件的序列化,卻鮮用XML的序列化。對於XML序列化,.NET中同樣提供了一個非常方便的工具XmlSerializer,其可以很方便的將對象序列化為XML文件或將XML文件反序列化為對象。但是XML序列化與二進制序列化卻又不少的區別,在剛開始的時候可能會遇到一些困惑。
【文章索引】
.NET提供了非常方便的XML序列化工具XmlSerializer,與二進制序列化工具BinaryFormatter不同,XmlSerializer位於System.Xml.Serialization。根據MSDN上對XmlSerializer的說明來看,“XML 序列化是將對象的公共屬性和字段轉換為序列格式(這里是指 XML)以便存儲或傳輸的過程。反序列化則是從 XML 輸出中重新創建原始狀態的對象。可以將序列化視為將對象的狀態保存到流或緩沖區的方法”,也就是說,我們可以直接用XmlSerializer序列化對象中的屬性和字段。
需要注意的是,只有public的屬性和字段才是可以被序列化的,如果設置的為internal或者private的屬性或字段都是不能被序列化的。當然,要序列化的對象的類也必須是public的,否則會拋出下列的異常:
除此之外,要想序列化對象中的字段或者屬性,還需要保證字段和屬性是可讀可寫的。例如,readonly的字段是不可以序列化的,沒有get或set訪問器的屬性也是不可以序列化的(當然你可以選擇在set訪問器里什么也不寫,那么雖然能序列化,但是反序列化的時候就成空的啦)。當然,static和const的字段和屬性也是不會被序列化的,標記為[Obsolete]的也不會被序列化。此外,除了要求類是public的以外,還需要其有一個無參的構造方法,否則也會拋出異常。
關於XmlSerializer的使用,其實非常簡單,只需要幾行代碼即可實現將一個對象序列化:

1 void SaveToFile(String filePath, Object obj) 2 { 3 FileStream fs = null; 4 5 try 6 { 7 fs = new FileStream(filePath, FileMode.Create, FileAccess.Write); 8 XmlSerializer xs = new XmlSerializer(obj.GetType()); 9 10 xs.Serialize(fs, obj); 11 } 12 finally 13 { 14 if (fs != null) 15 { 16 fs.Close(); 17 } 18 } 19 }
或者,反序列化。

1 T LoadFromFile<T>(String filePath) 2 { 3 FileStream fs = null; 4 5 try 6 { 7 fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); 8 XmlSerializer xs = new XmlSerializer(typeof(T)); 9 10 return (T)xs.Deserialize(fs); 11 } 12 finally 13 { 14 if (fs != null) 15 { 16 fs.Close(); 17 } 18 } 19 }
如果按上述的代碼進行序列化,則可以將對象中的所有公共屬性和字段都序列化進XML文件中。對象中的每個屬性或字段都會序列化為一個子元素,如果對象中還有其他的對象或者數組等還會有更深的子元素。但是有時候我們可能除了子元素外還需要序列化節點的屬性,或者需要修改映射的名稱等等,那么我們就需要對類中的屬性或者字段添加特性(Attributes)了。
與XML序列化相關的常見的特性有:
1、[XmlAttribute]:可以將指定字段或屬性序列化為元素的屬性,而不是子元素。除了直接在字段或屬性上方直接寫“[XmlAttribute]”外,還可以對其傳入參數,例如“[XmlAttribute("identity")]”,可以改變映射的名稱。例如:
[XmlAttribute("identity")] public Int32 ID;
類定義及序列化后的結果如下:

public class Student { [XmlAttribute("identity")] public Int32 ID; public String Name; }

<?xml version="1.0"?> <Student xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" identity="1"> <Name>姓名</Name> </Student>
2、[XmlElement]:雖然默認就可以將字段或屬性序列化為子元素,但是如果要修改映射的名稱,還是需要借助這個特性的。與[XmlAttribute]類似,其也可以不傳入或傳入參數,當不傳入參數時,與不加該特性相同;當傳入參數時,則可以修改映射的名稱。例如:
[XmlElement("UserName")] public String Name;
類定義及序列化后的結果如下:

public class Student { public Int32 ID; [XmlElement("UserName")] public String Name; }

<?xml version="1.0"?> <Student xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <ID>1</ID> <UserName>姓名</UserName> </Student>
3、[XmlText]:除了能序列化為屬性或者子元素外,還可以直接作為該元素的文本內容(InnerText),例如有個類Student,有一個ID我們希望序列化為屬性,還有一個Name我們希望直接作為Student的內容而不是子元素,那么我們就可以在Name上使用[XmlText]了。例如:
[XmlText] public String Name;
類定義及序列化后的結果如下:

public class Student { [XmlAttribute] public Int32 ID; [XmlText] public String Name; }

<?xml version="1.0"?> <Student xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ID="1">姓名</Student>
4、[XmlIgnore]:如果一個屬性或字段我們不希望序列化(比如該屬性是通過其他字段獲取到的,並沒有set訪問器等等),那么我們可以通過[XmlIgnore]來讓序列化器來忽略這個屬性或字段。例如:
[XmlIgnore] public Int32 NameLength { get { return this.Name.Length; } }
類定義及序列化后的結果如下:

public class Student { public Int32 ID; public String Name; [XmlIgnore] public Int32 NameLength { get { return this.Name.Length; } } }

<?xml version="1.0"?> <Student xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <ID>1</ID> <Name>姓名</Name> </Student>
5、[XmlArray]:如果需要序列化一個數組或者List等,但是需要修改映射的名稱,那么我們就會用到[XmlArray]。需要注意的是,雖然數組等序列化出來的也是一個子元素,但是盡量不要用[XmlElement],否則數組里的每一個元素相當於對象的直接子元素(除非這個類本身序列化成子元素的就很少或沒有,類似使用[XmlText]的情況),下邊會給出對比。與[XmlElement]等相類似,如果不設置參數的話,那么與不添加特性相同;而對其設置參數后,則可以修改子元素的名稱。例如:
[XmlArray("AllScore")] public List<Int32> Scores;
類定義及序列化后的結果如下:

public class Student { public Int32 ID; public String Name; [XmlArray("AllScore")] public List<Int32> Scores; [XmlElement("FamilyMember")] public List<String> FamilyNames; }

<?xml version="1.0"?> <Student xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <ID>1</ID> <Name>姓名</Name> <AllScore> <int>80</int> <int>75</int> <int>89</int> </AllScore> <FamilyMember>父親姓名</FamilyMember> <FamilyMember>母親姓名</FamilyMember> </Student>
6、[XmlArrayItem]:上述雖然對數組的名稱進行了映射,但是數組里每一個元素的名稱卻沒有定義,所以導致的結果是,所有數組里元素的名稱都是按照類型名稱來的,比如Int32類型的元素的元素名就是int等等,所以我們需要使用[XmlArrayItem]特性進行設置,增加上參數以后就可以映射數組里元素的名稱了。例如:
[XmlArray("AllScore")] [XmlArrayItem("Score")] public List<Int32> Scores;
類定義及序列化后的結果如下:

public class Student { public Int32 ID; public String Name; [XmlArray("AllScore")] [XmlArrayItem("Score")] public List<Int32> Scores; }

<?xml version="1.0"?> <Student xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <ID>1</ID> <Name>姓名</Name> <AllScore> <Score>80</Score> <Score>75</Score> <Score>89</Score> </AllScore> </Student>
6、[XmlRoot]:對於類的名稱如果要映射的話,就不能使用上述任何一個特性了,因為如果對類使用的話會提示“它只在“property, indexer, field, param, return”聲明中有效”。這時候我們就需要[XmlRoot]這個特性,同樣的,對其設置參數,即可以完成對類名稱的映射。例如:
[XmlRoot("StudentInfo")] public class Student { }
上述幾個特性除了[XmlIgnore]以外,都還支持設置命名參數,如下圖。
例如[XmlElement]、[XmlArray]等可以設置Order參數,就是可以強制設置子元素出現位置的先后順序,例如:
public class Student { [XmlElement(Order = 2)] public Int32 ID; [XmlElement(Order = 1)] public String Name; }
不像BinaryFormatter,XML序列化是有很多東西是不能序列化的,比如眾所周知的Dictionary,我們其實可以通過.NET的源代碼來查看到底哪些東西不能序列化為XML。通過序列化Dictionary拋出異常,可以找到如下這個類的相關方法,在.NET源代碼的“Source\Net\3.5.50727.3053\DEVDIV\depot\DevDiv\releases\whidbey\netfxsp\ndp\fx\src\Xml\System\Xml\Serialization\Types.cs”目錄下可以找到。
根據源代碼,可以發現不能序列化的有以下的類型:
1、繼承IDictionary接口的類型,這個眾所周知了。.NET判斷凡是實現了ICollection接口的都要去System.Xml.Serialization.TypeScope.GetDefaultIndexer()判斷是否繼承了IDictionary接口,如果繼承了則拋出異常。
2、維度大於1的數組,在System.Xml.Serialization.TypeScope.ImportTypeDesc()里有判斷維度是否大於1,如果維度大於1就拋出異常。
3、ValueType類型,別擔心,這個不是指所有值類型的不能被序列化,源代碼里判斷的是“type == typeof(ValueType)”,所以特指ValueType類型的不能被序列化。p.s.我才知道竟然可以創建ValueType類型的變量。
此外,只要滿足第一節里提到的XML序列化的要求的,都能被序列化,整理如下:
1、定義的類或者結構體或者枚舉必須為public,類或結構體必須有無參的構造方法。比如System.Drawing.Font就無法實現序列化,因為其沒有無參的構造方法。
2、要序列化的字段或屬性必須為public,並且不能為static,標記為[Obsolete]的不會被序列化。字段不能為readonly或const,屬性必須同時有set和get訪問器。比如System.Drawing.Color序列化后不包含任何內容,因為其所有的公有屬性全部只有get訪問器,沒有set訪問器。
如果對序列化后的XML文件的輸出格式有要求,比如要修改XML文件的編碼、設置XML文件縮進、設置XML的命名空間等等,那么我們可以通過XmlWriter來實現我們的要求。XmlWriter可以通過XmlWriter.Create創建,可以寫入到流、或者直接寫入到文件路徑或者寫入到一個StringBuilder中。
設置XML文件的編碼、縮進等可以通過創建XmlWriterSettings來設置,例如可以將縮進字符以及換行字符去除以達到減少文件大小的目的。
1 XmlWriterSettings settings = new XmlWriterSettings(); 2 settings.Encoding = Encoding.ASCII; 3 settings.IndentChars = ""; 4 settings.NewLineChars = ""; 5 //或者也可以這樣 6 //settings.Indent = false; 7 //settings.NewLineHandling = NewLineHandling.None; 8 9 XmlWriter xw = XmlWriter.Create(fs, settings); 10 XmlSerializer xs = new XmlSerializer(obj.GetType()); 11 xs.Serialize(xw, obj);
而對於設置XML命名空間,則可以創建XmlSerializerNamespaces,比如可以添加空的命名空間以取消默認設置的命名空間。
1 XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces(); 2 namespaces.Add(String.Empty, String.Empty); 3 4 //省略部分代碼 5 6 xs.Serialize(xw, obj, namespaces);
【相關鏈接】
- XmlSerializer 類:http://msdn.microsoft.com/zh-cn/library/system.xml.serialization.xmlserializer.aspx
- 在.net中序列化讀寫xml方法的總結:http://www.cnblogs.com/fish-li/archive/2013/05/05/3061816.html