今天的主角是.net的Xml序列化。
在主角出现前,先回想一下,平时什么地方用了Xml序列化吧:
第一个想到的当然是Web Service和更进一步的WCF,没有Xml序列化的话,就需要手动处理Soap协议的各种输入和输出,其复杂性将会成倍的增长。
第二个想到的就是Xml序列化其实就是一个Xml与对象之间的桥梁,可以把一个实例Xml变成一个实例对象,也可以把一个实例对象变成一个实例Xml,这在需要持久化的场合非常有用。
工具
工欲善其事,必先利其器。首先来看看关于Xml序列化的工具吧。
这些工具通常在X:\Program Files\Microsoft SDKs\Windows\v6.0A\bin目录下,其中和Xml序列关系比较大的有xsd.exe、wsdl.exe、svcutil.exe。当然其他工具在.net里面也是非常重要的,可以在这里察看所有工具的用途和使用方式。这里重点要用到的是xsd.exe。
当然这个工具有三种用法,分别是:
- Xml First,先有Xml实例,适合先想好Xml是什么样,或者已经有Xml实例的情况
- Xsd First,先有Xsd,适合于可以获得Xsd,或者熟悉Xsd的人,并且对Xml有很强的控制欲的人(某人飘过)
- Class First,先有c#类型,适合于先有c#代码的情况
接下来将分别介绍这3种方式的。
Xml First
这种情况首先有一个Xml实例,例如:
<?xml version="1.0" encoding="utf-8" ?> <persons> <person name="Zhenway, Yan"> <goodat>Xml</goodat> <goodat>Reflection</goodat> </person> <person name="Allen, Lee"> <goodat>Ruby</goodat> <goodat>F#</goodat> <goodat>Windows Mobile</goodat> <goodat>Linq</goodat> </person> </persons>
利用Xsd命令:“xsd XmlFirst.xml”,就可以根据这个实例获得xsd(当然不会是非常精确的,但基本上能用):
<?xml version="1.0" encoding="utf-8"?> <xs:schema id="persons" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xs:element name="persons" msdata:IsDataSet="true" msdata:Locale="en-US"> <xs:complexType> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="person"> <xs:complexType> <xs:sequence> <xs:element name="goodat" nillable="true" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:simpleContent msdata:ColumnName="goodat_Text" msdata:Ordinal="0"> <xs:extension base="xs:string"> </xs:extension> </xs:simpleContent> </xs:complexType> </xs:element> </xs:sequence> <xs:attribute name="name" type="xs:string" /> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element> </xs:schema>
当然,如果对这个Xsd不太满意的话,还可以修改一下。这样就把Xml First转换成Xsd First。
Xsd First
这里首先需要一个Xsd(某个控制欲极强的人重新写了一下xsd)
<?xml version="1.0" encoding="utf-8"?> <xs:schema id="persons" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:complexType name="Person"> <xs:sequence> <xs:element name="goodat" type="xs:string" minOccurs="1" maxOccurs="unbounded"/> </xs:sequence> <xs:attribute name="name" type="xs:string"/> </xs:complexType> <xs:complexType name="Persons"> <xs:sequence> <xs:element name="person" type="Person" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> <xs:element name="persons" type="Persons"/> </xs:schema>
这里要求每个person的goodat至少要有一项。
然后利用xsd命令:“xsd XsdFirst.xsd /c”,这样就可以获得一个cs文件,整理后,如下:
using System; using System.CodeDom.Compiler; using System.ComponentModel; using System.Diagnostics; using System.Xml.Schema; using System.Xml.Serialization; [GeneratedCodeAttribute("xsd", "2.0.50727.1432")] [SerializableAttribute()] [DebuggerStepThroughAttribute()] [DesignerCategoryAttribute("code")] [XmlRootAttribute("persons", Namespace = "", IsNullable = false)] public partial class Persons { private Person[] personField; [System.Xml.Serialization.XmlElementAttribute("person", Form = XmlSchemaForm.Unqualified)] public Person[] person { get { return this.personField; } set { this.personField = value; } } } [GeneratedCodeAttribute("xsd", "2.0.50727.1432")] [SerializableAttribute()] [DebuggerStepThroughAttribute()] [DesignerCategoryAttribute("code")] public partial class Person { private string[] goodatField; private string nameField; [XmlElementAttribute("goodat", Form = XmlSchemaForm.Unqualified)] public string[] goodat { get { return this.goodatField; } set { this.goodatField = value; } } [XmlAttributeAttribute()] public string name { get { return this.nameField; } set { this.nameField = value; } } }
这样就可以获得一个类型与这个Xsd对应,在对象实例与这个Xsd实例之间建立一座桥梁。
Class First
这种情况适合于先有类型,然后想持久化的情况,例如拥有一个下列的类型:
[XmlRoot("persons")] public class ClassFirst { [XmlElement("person")] public Person[] PersonCollection { get; set; } } public class Person { [XmlAttribute("name")] public string Name { get; set; } [XmlElement("goodat")] public string[] GoodAt { get; set; } }
Build以后,执行命令:“xsd ClassFirstSample.dll”,就可以获得这样一个xsd:
<?xml version="1.0" encoding="utf-8"?> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="persons" nillable="true" type="ClassFirst" /> <xs:complexType name="ClassFirst"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="unbounded" name="person" type="Person" /> </xs:sequence> </xs:complexType> <xs:complexType name="Person"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="unbounded" name="goodat" type="xs:string" /> </xs:sequence> <xs:attribute name="name" type="xs:string" /> </xs:complexType> <xs:element name="Person" nillable="true" type="Person" /> </xs:schema>
这个xsd就是序列化出来的xml的Schema,基本上与。
XmlSerializer
前面的三种方式都只是在OOP与Xml之间建立一个契约,现在来介绍这个桥梁——XmlSerializer
废话就不说了,先来看看如何把对象转换成xml:
XmlSerializer serializer = new XmlSerializer(typeof(Persons)); serializer.Serialize(Console.Out, new Persons { person = new Person[] { new Person { name = "Zhenway, Yan", goodat = new string[] { "Reflection", "Xml" }, }, new Person { name = "Allen, Lee", goodat = new string[] { "Ruby", "F#", "Windows Mobile", "Linq" }, }, } });
来看看输出:
很好,接下来看看如何反过来,从Xml获得对象实例:
XmlSerializer serializer = new XmlSerializer(typeof(Persons)); using (var reader = File.OpenText("XmlFirst.xml")) { Persons ps = (Persons)serializer.Deserialize(reader); foreach (var p in ps.person) { Console.WriteLine("name='{0}', good at={1}", p.name, string.Join(", ", p.goodat)); } }
看看输出:
不过需要注意的一点是,如果需要反序列化对象的话,需要对临时目录(根据Windows的TEMP环境变量定义)的写入权限。
另外,XmlSerializer会自动生成一个Assembly用于加速序列化和反序列化,不过要注意由于AppDomain无法单独卸载一个Assembly的特性,所以当产生过多的Assembly时,就会导致内存占用过多。
尽管XmlSerializer也会尽量利用现有的Assembly,不过这仅仅发生在(Type)构造函数,和(Type,String)构造函数时才会发生,而其他构造函数将再次创建Assembly,如果放置在循环中,这样将导致AppDomain中Assembly数量激增,因此缓存XmlSerializer在某些场合下是非常必要的。
另外msdn上对XmlSerializer的描述有一处非常特别的地方,“此类型是线程安全的”,这在整个msdn中并不多见,也就是说,不用对缓存的XmlSerializer对象做任何的同步处理。
更多控制
XmlSerializer本身支持很多扩展,其中包括使用属性控制 XML 序列化,和更加可定制化的IXmlSerializable接口,这里限于篇幅就省略相关的内容。