C# 序列化(Serialize)、反序列化(Deserialize)
序列化又称串行化,是.NET运行时环境用来支持用户定义类型的流化的机制。其目的是以某种存储形成使自定义对象持久化,或者将这种对象从一个地方传输到另一个地方。
.NET框架提供了两种串行化的方式:1、是使用BinaryFormatter进行串行化;2、使用SoapFormatter进行串行化;3、使用XmlSerializer进行串行化。第一种方式提供了一个简单的二进制数据流以及某些附加的类型信息,而第二种将数据流格式化为XML存储;第三种其实和第二种差不多也是XML的格式存储,只不过比第二种的XML格式要简化很多(去掉了SOAP特有的额外信息)。
可以使用[Serializable]属性将类标志为可序列化的。如果某个类的元素不想被序列化,1、2可以使用[NonSerialized]属性来标志,2、可以使用[XmlIgnore]来标志。
二进制序列器:
对象序列化之后是二进制形式的,通过BinaryFormatter类来实现的,这个类位于System.Runtime.Serialization.Formatters.Binary命名空间下
[Serializable] //使对象可序列化(必须添加)
特性
程序集,类,方法,属性都可以使用特性
BinaryFormatter //创建二进制序列化器
Serialize(Stream(流),object(序列化对象))
流:可以理解成打通内存和硬盘的一个工具
输入流:从硬盘到内存
输出流:从内存到硬盘
XML序列化器:
对象序列化之后的结果符合SOAP协议,也就是可以通过SOAP?协议传输,通过System.Runtime.Serialization.Formatters.Soap命名空间下的SoapFormatter类来实现的。
SOAP序列化器:
对象序列化之后的结果是XML形式的,通过XmlSerializer?类来实现的,这个类位于System.Xml.Serialization命名空间下。XML序列化不能序列化私有数据。
反序列化:
将流转换为对象
Disk(硬盘)--->Cache(内存)
BinaryFormatter //创建二进制序列化器
Deserialize(Stream(流))//返回object类型
1、使用BinaryFormatter进行串行化
我使用的例子是 System.Windows.Forms.TreeNode类;
[System.ComponentModel.TypeConverter(typeof(System.Windows.Forms.TreeNodeConverter))] [System.Serializable] public class TreeNode : MarshalByRefObject, ICloneable, System.Runtime.Serialization.ISerializable
然后我们对这个TreeNode进行 序列化 和 反序列化。
我们先定义控件变量
treevMainForm : 存放TreeNode节点控件
// 序列化
public void SerializeNow() { TreeNodeCollection c = treevMainFomr.Nodes; FileStream fileStream = new FileStream("c:\\temp.dat", FileMode.Create); BinaryFormatter b = new BinaryFormatter(); b.Serialize(fileStream, c); fileStream.Close(); }
// 反序列化 public void DeSerializeNow() { TreeNodeCollection c = new TreeNodeCollection(); FileStream fileStream = new FileStream("c:\\temp.dat", FileMode.Open, FileAccess.Read, FileShare.Read); BinaryFormatter b = new BinaryFormatter(); c = b.Deserialize(fileStream) as TreeNodeCollection; fileStream.Close(); }
2、使用SoapFormatter进行串行化
和BinaryFormatter类似,我们只需要做一下简单修改即可:
a.将using语句中的.Formatter.Binary改为.Formatter.Soap;
b.将所有的BinaryFormatter替换为SoapFormatter.
c.确保报存文件的扩展名为.xml
经过上面简单改动,即可实现SoapFormatter的串行化,这时候产生的文件就是一个xml格式的文件。
3、使用XmlSerializer进行串行化
关于格式化器还有一个问题,假设我们需要XML,但是不想要SOAP特有的额外信息,那么我们应该怎么办呢?有两中方案:要么编写一个实现IFormatter接口的类,采用的方式类似于SoapFormatter类,但是没有你不需要的信息;要么使用库类XmlSerializer,这个类不使用Serializable属性,但是它提供了类似的功能。
如果我们不想使用主流的串行化机制,而想使用XmlSeralizer进行串行化我们需要做一下修改:
a.添加System.Xml.Serialization命名空间。
b.Serializable和NoSerialized属性将被忽略,而是使用XmlIgnore属性,它的行为与NoSerialized类似。
c.XmlSeralizer要求类有个默认的构造器,这个条件可能已经满足了。
下面看示例:
要序列化的类:
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Xml.Serialization; [Serializable] public class Person { private string name; public string Name { get { return name; } set { name = value; } } public string Sex; public int Age = 31; public Course[] Courses; public Person() { } public Person(string Name) { name = Name; Sex = "男"; } } [Serializable] public class Course { public string Name; [XmlIgnore] public string Description; public Course() { } public Course(string name, string description) { Name = name; Description = description; } }
序列化和反序列化方法:
public void XMLSerialize() { Person c = new Person("cyj"); c.Courses = new Course[2]; c.Courses[0] = new Course("英语", "交流工具"); c.Courses[1] = new Course("数学","自然科学"); XmlSerializer xs = new XmlSerializer(typeof(Person)); Stream stream = new FileStream("c:\\cyj.XML",FileMode.Create,FileAccess.Write,FileShare.Read); xs.Serialize(stream,c); stream.Close(); } public void XMLDeserialize() { XmlSerializer xs = new XmlSerializer(typeof(Person)); Stream stream = new FileStream("C:\\cyj.XML",FileMode.Open,FileAccess.Read,FileShare.Read); Person p = xs.Deserialize(stream) as Person; Response.Write(p.Name); Response.Write(p.Age.ToString()); Response.Write(p.Courses[0].Name); Response.Write(p.Courses[0].Description); Response.Write(p.Courses[1].Name); Response.Write(p.Courses[1].Description); stream.Close(); }
这里Course类的Description属性值将始终为null,生成的xml文档中也没有该节点,如下图:
<?xml version="1.0"?> <Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Sex>男</Sex> <Age>31</Age> <Courses> <Course> <Name>英语</Name> <Description>交流工具</Description> </Course> <Course> <Name>数学</Name> <Description>自然科学</Description> </Course> </Courses> <Name>cyj</Name> </Person>
4、自定义序列化
如果你希望让用户对类进行串行化,但是对数据流的组织方式不完全满意,那么可以通过在自定义类中实现接口来自定义串行化行为。这个接口只有一个方法,GetObjectData. 这个方法用于将对类对象进行串行化所需要的数据填进SerializationInfo对象。你使用的格式化器将构造SerializationInfo对象,然后在串行化时调用GetObjectData. 如果类的父类也实现了ISerializable,那么应该调用GetObjectData的父类实现。
如果你实现了ISerializable,那么还必须提供一个具有特定原型的构造器,这个构造器的参数列表必须与GetObjectData相同。这个构造器应该被声明为私有的或受保护的,以防止粗心的开发人员直接使用它。
示例如下:
实现ISerializable的类:
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; /// <summary> /// Employee 的摘要说明 /// </summary> [Serializable] public class Employee:ISerializable { public int EmpId=100; public string EmpName="刘德华"; [NonSerialized] public string NoSerialString = "NoSerialString-Test"; public Employee() { // // TODO: 在此处添加构造函数逻辑 // } private Employee(SerializationInfo info, StreamingContext ctxt) { EmpId = (int)info.GetValue("EmployeeId", typeof(int)); EmpName = (String)info.GetValue("EmployeeName",typeof(string)); //NoSerialString = (String)info.GetValue("EmployeeString",typeof(string)); } public void GetObjectData(SerializationInfo info, StreamingContext ctxt) { info.AddValue("EmployeeId", EmpId); info.AddValue("EmployeeName", EmpName); //info.AddValue("EmployeeString", NoSerialString); } }
序列化和反序列化方法:
public void OtherEmployeeClassTest() { Employee mp = new Employee(); mp.EmpId = 10; mp.EmpName = "邱枫"; mp.NoSerialString = "你好呀"; Stream steam = File.Open("c:\\temp3.dat", FileMode.Create); BinaryFormatter bf = new BinaryFormatter(); Response.Write("Writing Employee Info:"); bf.Serialize(steam,mp); steam.Close(); mp = null; //反序列化 Stream steam2 = File.Open("c:\\temp3.dat", FileMode.Open); BinaryFormatter bf2 = new BinaryFormatter(); Response.Write("Reading Employee Info:"); Employee mp2 = (Employee)bf2.Deserialize(steam2); steam2.Close(); Response.Write(mp2.EmpId); Response.Write(mp2.EmpName); Response.Write(mp2.NoSerialString); }
在格式化程序上调用 Serialize 方法时,对象序列化按照以下规则进行:
检查格式化程序是否有代理选取器。如果有,检查代理选取器是否处理指定类型的对象。如果选取器处理此对象类型,将在代理选取器上调用 ISerializable.GetObjectData。
如果没有代理选取器或有却不处理此类型,将检查是否使用 Serializable 属性对对象进行标记。如果未标记,将会引发 SerializationException。
如果对象已被正确标记,将检查对象是否实现了 ISerializable。如果已实现,将在对象上调用 GetObjectData。
如果对象未实现 Serializable,将使用默认的序列化策略,对所有未标记为 NonSerialized 的字段都进行序列化。
版本控制
.NET 框架支持版本控制和并排执行,并且,如果类的接口保持一致,所有类均可跨版本工作。由于序列化涉及的是成员变量而非接口,所以,在向要跨版本序列化的类中添加成员变量,或从中删除变量时,应谨慎行事。特别是对于未实现 ISerializable 的类更应如此。若当前版本的状态发生了任何变化(例如添加成员变量、更改变量类型或更改变量名称),都意味着如果同一类型的现有对象是使用早期版本进行序列化的,则无法成功对它们进行反序列化。
如果对象的状态需要在不同版本间发生改变,类的作者可以有两种选择:
实现 ISerializable。这使您可以精确地控制序列化和反序列化过程,在反序列化过程中正确地添加和解释未来状态。
使用 NonSerialized 属性标记不重要的成员变量。仅当预计类在不同版本间的变化较小时,才可使用这个选项。例如,把一个新变量添加至类的较高版本后,可以将该变量标记为 NonSerialized,以确保该类与早期版本保持兼容。
序列化规则
由于类编译后便无法序列化,所以在设计新类时应考虑序列化。需要考虑的问题有:是否必须跨应用程序域来发送此类?是否要远程使用此类?用户将如何使用此类?也许他们会从我的类中派生出一个需要序列化的新类。只要有这种可能性,就应将类标记为可序列化。除下列情况以外,最好将所有类都标记为可序列化:
所有的类都永远也不会跨越应用程序域。如果某个类不要求序列化但需要跨越应用程序域,请从 MarshalByRefObject 派生此类。
类存储仅适用于其当前实例的特殊指针。例如,如果某个类包含非受控的内存或文件句柄,请确保将这些字段标记为 NonSerialized 或根本不序列化此类。
某些数据成员包含敏感信息。在这种情况下,建议实现 ISerializable 并仅序列化所要求的字段。
以上部分内容并非原创有参考其他文章。