【题外话】html
之前虽然经常使用.NET中的序列化,可是经常使用的BinaryFormatter,也就是二进制文件的序列化,却鲜用XML的序列化。对于XML序列化,.NET中一样提供了一个很是方便的工具XmlSerializer,其能够很方便的将对象序列化为XML文件或将XML文件反序列化为对象。可是XML序列化与二进制序列化却又很多的区别,在刚开始的时候可能会遇到一些困惑。数组
【文章索引】ide
.NET提供了很是方便的XML序列化工具XmlSerializer,与二进制序列化工具BinaryFormatter不一样,XmlSerializer位于System.Xml.Serialization。根据MSDN上对XmlSerializer的说明来看,“XML 序列化是将对象的公共属性和字段转换为序列格式(这里是指 XML)以便存储或传输的过程。反序列化则是从 XML 输出中从新建立原始状态的对象。能够将序列化视为将对象的状态保存到流或缓冲区的方法”,也就是说,咱们能够直接用XmlSerializer序列化对象中的属性和字段。ui
须要注意的是,只有public的属性和字段才是能够被序列化的,若是设置的为internal或者private的属性或字段都是不能被序列化的。固然,要序列化的对象的类也必须是public的,不然会抛出下列的异常:this
除此以外,要想序列化对象中的字段或者属性,还须要保证字段和属性是可读可写的。例如,readonly的字段是不能够序列化的,没有get或set访问器的属性也是不能够序列化的(固然你能够选择在set访问器里什么也不写,那么虽然能序列化,可是反序列化的时候就成空的啦)。固然,static和const的字段和属性也是不会被序列化的,标记为[Obsolete]的也不会被序列化。此外,除了要求类是public的之外,还须要其有一个无参的构造方法,不然也会抛出异常。编码
关于XmlSerializer的使用,其实很是简单,只须要几行代码便可实现将一个对象序列化:spa
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 }
或者,反序列化。.net
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序列化相关的常见的特性有:
一、[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>
二、[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>
三、[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>
四、[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>
五、[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>
六、[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>
六、[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”目录下能够找到。
根据源代码,能够发现不能序列化的有如下的类型:
一、继承IDictionary接口的类型,这个众所周知了。.NET判断凡是实现了ICollection接口的都要去System.Xml.Serialization.TypeScope.GetDefaultIndexer()判断是否继承了IDictionary接口,若是继承了则抛出异常。
二、维度大于1的数组,在System.Xml.Serialization.TypeScope.ImportTypeDesc()里有判断维度是否大于1,若是维度大于1就抛出异常。
三、ValueType类型,别担忧,这个不是指全部值类型的不能被序列化,源代码里判断的是“type == typeof(ValueType)”,因此特指ValueType类型的不能被序列化。p.s.我才知道居然能够建立ValueType类型的变量。
此外,只要知足第一节里提到的XML序列化的要求的,都能被序列化,整理以下:
一、定义的类或者结构体或者枚举必须为public,类或结构体必须有无参的构造方法。好比System.Drawing.Font就没法实现序列化,由于其没有无参的构造方法。
二、要序列化的字段或属性必须为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);
【相关连接】