.NET中XML序列化的總結


【題外話】

以前雖然常用.NET中的序列化,但是常用的BinaryFormatter,也就是二進制文件的序列化,卻鮮用XML的序列化。對於XML序列化,.NET中同樣提供了一個非常方便的工具XmlSerializer,其可以很方便的將對象序列化為XML文件或將XML文件反序列化為對象。但是XML序列化與二進制序列化卻又不少的區別,在剛開始的時候可能會遇到一些困惑。

 

【文章索引】

  1. XmlSerializer的作用
  2. 自定義XML結構的映射
  3. 不能序列化的內容
  4. 輸出格式的設置

 

【一、XmlSerializer的作用】

.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 }
View Code

或者,反序列化。

 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 }
View Code

 

【二、自定義XML結構的映射】

如果按上述的代碼進行序列化,則可以將對象中的所有公共屬性和字段都序列化進XML文件中。對象中的每個屬性或字段都會序列化為一個子元素,如果對象中還有其他的對象或者數組等還會有更深的子元素。但是有時候我們可能除了子元素外還需要序列化節點的屬性,或者需要修改映射的名稱等等,那么我們就需要對類中的屬性或者字段添加特性(Attributes)了。

與XML序列化相關的常見的特性有:

1、[XmlAttribute]:可以將指定字段或屬性序列化為元素的屬性,而不是子元素。除了直接在字段或屬性上方直接寫“[XmlAttribute]”外,還可以對其傳入參數,例如“[XmlAttribute("identity")]”,可以改變映射的名稱。例如:

[XmlAttribute("identity")]
public Int32 ID;

類定義及序列化后的結果如下:

public class Student
{
    [XmlAttribute("identity")]
    public Int32 ID;
    public String Name;
}
View Code
<?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>
View Code

2、[XmlElement]:雖然默認就可以將字段或屬性序列化為子元素,但是如果要修改映射的名稱,還是需要借助這個特性的。與[XmlAttribute]類似,其也可以不傳入或傳入參數,當不傳入參數時,與不加該特性相同;當傳入參數時,則可以修改映射的名稱。例如:

[XmlElement("UserName")]
public String Name;

類定義及序列化后的結果如下:

public class Student
{
    public Int32 ID;
    [XmlElement("UserName")]
    public String Name;
}
View Code
<?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>
View Code

3、[XmlText]:除了能序列化為屬性或者子元素外,還可以直接作為該元素的文本內容(InnerText),例如有個類Student,有一個ID我們希望序列化為屬性,還有一個Name我們希望直接作為Student的內容而不是子元素,那么我們就可以在Name上使用[XmlText]了。例如:

[XmlText]
public String Name;

類定義及序列化后的結果如下:

public class Student
{
    [XmlAttribute]
    public Int32 ID;
    [XmlText]
    public String Name;
}
View Code
<?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>
View Code

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; } }
}
View Code
<?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>
View Code

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;
}
View Code
<?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>
View Code

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;
}
View Code
<?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>
View Code

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]的不會被序列化。字段不能為readonlyconst,屬性必須同時有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);

 

【相關鏈接】

  1. XmlSerializer 類:http://msdn.microsoft.com/zh-cn/library/system.xml.serialization.xmlserializer.aspx
  2. 在.net中序列化讀寫xml方法的總結:http://www.cnblogs.com/fish-li/archive/2013/05/05/3061816.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM