使用XmlSerializer可以方便的將對象序列化為xml,實現應用之間的數據交互。但是XmlSerializer卻不能很好地序列化類型中的可空字段。
例如,有如下定義的類Person:
- [Serializable]
- [XmlRoot(ElementName = "Person")]
- public class Person
- {
- public string FirstName { get; set; }
- public string LastName { get; set; }
- public int? Age { get; set; }
- }
其中的Age屬性為Nullable int類型。
我們的實例化代碼如下所示:
- var person = new Person
- {
- FirstName = "First",
- };
- person.OutputXml(Console.Out);
其中方法OutputXml為擴展方法,使用XmlSerializer來序列化對象,具體定義為:
- public static void OutputXml<T>(this T dto, TextWriter textWriter)
- {
- var xmlTypeMapping = typeof(T);
- var serializer = new XmlSerializer(xmlTypeMapping);
- var xmlns = new XmlSerializerNamespaces();
- xmlns.Add(string.Empty, string.Empty);
- using (var writer = new XmlTextWriter(textWriter) { Formatting = Formatting.Indented })
- {
- serializer.Serialize(writer, dto, xmlns);
- }
- }
使用上述方法序列化對象person,得到的結果為:
注意到雖然Age屬性為空,卻仍然被序列化成 了古怪的xml,這往往不是所期望的結果。事實上同為空的LastName屬性的序列化正是大多數情況下我們所期望的行為——忽略為空的屬性。
另一方面,如果試圖序列化類屬性為xml 屬性(而非xml元素),則甚至不能工作。例如如果我們將Person類的定義修改成如下形式以便序列化為xml屬性:
- [Serializable]
- [XmlRoot(ElementName = "Person")]
- public class Person
- {
- [XmlAttribute]
- public string FirstName { get; set; }
- [XmlAttribute]
- public string LastName { get; set; }
- [XmlAttribute]
- public int? Age { get; set; }
- }
Xmlserializer甚至無法正常序列化上面同樣的person對象並拋出如下“XmlAttribute/XmlText cannot be used to encode complex types”的錯誤:
如何解決上述的2個問題呢?
1. 序列化可空屬性為XmlElement
為了在序列化可空屬性的時候忽略空值的古怪輸出,我們可以在Person類中定義一個返回bool的特殊方法ShouldSerializeAge,並在其中實現定義我們的序列化規則:
- [Serializable]
- [XmlRoot(ElementName = "Person")]
- public class Person
- {
- public string FirstName { get; set; }
- public string LastName { get; set; }
- public int? Age { get; set; }
- public bool ShouldSerializeAge()
- {
- return Age.HasValue;
- }
- }
在這里我們定義序列化規則為:序列化非空Age。注意該方法的名字一定是以ShouldSerialize開頭並連接上需要自定義規則的屬性名稱。這樣序列化出來的person對象為:
空的Age和空的LastName一樣在序列化時被忽略了輸出。正是我們期望的結果!
2. 序列化可空屬性為XmlAttribute
由於可空屬性無法被直接序列化為XmlAttribute,我們需要采用間接的辦法——定義間接屬性。此時我們可以如下定義Person類:
- [Serializable]
- [XmlRoot(ElementName = "Person")]
- public class Person
- {
- [XmlAttribute]
- public string FirstName { get; set; }
- [XmlAttribute]
- public string LastName { get; set; }
- [XmlIgnore] // (4)
- public int? Age { get; set; }
- [XmlAttribute(AttributeName = "Age")] // (1)
- public string AgeValue
- {
- get
- {
- // (2)
- return Age.HasValue ? Age.Value.ToString() : null;
- }
- set
- {
- int result;
- // (3)
- Age = int.TryParse(value, out result) ? result : (int?) null;
- }
- }
- }
注意類中注釋的部分:
- 為原可空屬性定義一個“虛擬”的屬性,該屬性可為任意名稱任意類型(示例里定義的為string類型的AgeValue),但注意要將該屬性的序列化名稱置為原可空屬性Age。
- 在get方法里根據Age是否為空將其轉化為AgeValue。
- 在set方法里將輸入的value轉化為合適的值賦給可空屬性Age。
- 將原Age屬性標記為XmlIgnore,使XmlSerializer在序列化時忽略序列化該屬性。
使用上述方法,我們可以將Age序列化為XmlAttribute了,雖然實際上我們是“騙”了XmlSerializer並使用另一個屬性作為Age屬性的值。
因此,當person對象中的Age為空時,我們得到如下的xml結果:
而當person對象中的Age不為空時,我們也可以正常的用XmlAttribute來表示Age的值了。