序列化的定义
序列化:描述了持久化一个对象的状态到流的过程。被持久化的数据次序包含所有以后需要用来重建(反序列化)对象状态所必须的信息。
从定义可以看出2点
1.序列化的用途:持久化一个对象
2.序列化的优点:序列化是双向的(序列化与反序列化),因为被序列化的数据包含所有以后用来重新对象状态的信息。
下面我们先来看一个序列化的简单的例子,来说明上述的两点。
/// <summary> /// 用户数据类 /// 记录用户喜欢的字体,与字体大小 /// </summary>
[Serializable] class UserData { public string FontFamily { get; set; } public string FontSize { get; set; } public override string ToString() { return string.Format("字体:{0};字体大小:{1}", FontFamily, FontSize); } }
static void Main(string[] args) { UserData userData = new UserData(); userData.FontSize = "16px"; userData.FontFamily = "微软雅黑"; //序列化为二进制流的操作类 BinaryFormatter formatter = new BinaryFormatter(); using (MemoryStream ms = new MemoryStream()) { //序列化用户数据,保存在内存中 formatter.Serialize(ms, userData); //反序列化数据,并且展示 ms.Position = 0; serData desUserData = formatter.Deserialize(ms) as UserData; Console.WriteLine(desUserData.ToString()); } }
上面的示例代码,展示了序列化用户数据与反序列化用户数据的过程。
序列化数据为什么可以进行反序列化(也就是说,为什么反序列化时,数据彼此之间的关系不会出错(is-a(继承的关系);has-a(从属关系))),这就要说到对象图的作用了。
对象图
当对象被序列化时,CLR将处理所有相关的对象,一组关联对象被总称为一个对象图。它提供了一种很简明的方式来记载一组对象的引用关系。对象图中的每个对象被赋予一个独有的数值类型的值,这个值可任意地赋给对象图中的成员,以此来记录对象的依赖关系。
上图可知,Order引用了Product,OnlineOrder引用了Order和Product。在CLR中,这种关系表示为:[Order 3,ref 2],[Product 2],[OnlineOrder 1,ref 3,ref 2]。当序列化OnlineOrder时,根据对象图,使Order和Product参与其中。(注意XmlSerializer不使用对象图。)
总结:对象图就是在序列化时,记录各个对象之间的关系。当反序列化时,通过记录的关系,将序列化数据还原成本来的面貌。
自定义序列化
大多数情况我们只要采用.NET提供的序列化方式,就可以完成我们的需求。即:
1.需要序列化的类添加[Serializable]特性。注意:该类所关联的所有数据(基类、需要参与序列化的属性)都必须支持序列化
2.选用恰当的序列化格式化程序(BinaryFormatter:二进制流;SoapFormatter:SOAP消息;XmlSerializer:XML文件)
当.NET提供给我们的序列化方式无法满足我们的需求时,就需要使用ISerializable自定义序列化。
例如:被序列化为流的字符串对象必须全是大写字母;从流中反序列化为小写字母。
首先我们来了解一下ISerializable接口
1.ISerializable所属命名空间:
using System.Runtime.Serialization;
2.ISerializable接口的定义:
// 摘要: // 允许对象控制其自己的序列化和反序列化过程。 [ComVisible(true)] public interface ISerializable { // 摘要: // 使用将目标对象序列化所需的数据填充 System.Runtime.Serialization.SerializationInfo。 // // 参数: // info: // 要填充数据的 System.Runtime.Serialization.SerializationInfo。 // // context: // 此序列化的目标(请参见 System.Runtime.Serialization.StreamingContext)。 // // 异常: // System.Security.SecurityException: // 调用方没有所要求的权限。 [SecurityCritical] void GetObjectData(SerializationInfo info, StreamingContext context); }
从接口的定义可以看出,该接口需要实现一个void GetObjectData(SerializationInfo info, StreamingContext context);方法。
a.该方法的用途:
序列化格式化程序进行序列化时,会调用该方法。(在该方法中可以对序列化的过程,进行一些干预。)
b.该方法有两个参数:
第一个参数:以键/值对的形式包含了所有参与序列化的数据、以及填充序列化的方法(序列化与反序列化基本就是通过该对象(SerializationInfo)实现。)
第二个参数:包含了序列化流的源的相关信息(一般用不到)
3.实现自定义序列化的类,必须提供一个如下签名的构造函数,以允许运行库引擎设置对象的状态
//受保护的构造函数,以确保不熟悉对象技术的新用户不能用这种方式简历对象 protected 构造函数名(SerializationInfo si, StreamingContext ctx) {}
该构造函数的用途:
序列化格式化程序进行反序列化时,会调用该方法(在该方法中可以对反序列化的过程,进行一些干预。)
4.实现代码
[Serializable] class UserData : ISerializable { public string Like { get; set; } public string Taste { get; set; } public UserData() { } protected UserData(SerializationInfo si, StreamingContext sc) { this.Like = si.GetString("Like").ToLower(); this.Taste = si.GetString("Taste").ToLower(); } public override string ToString() { return string.Format("喜好:{0};口味:{1}", Like, Taste); } public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Like", this.Like.ToUpper()); info.AddValue("Taste", this.Taste.ToUpper()); } }
static void Main(string[] args) { UserData userData = new UserData(); userData.Like = "Purple"; userData.Taste = "Mango"; SoapFormatter formatter = new SoapFormatter(); using(Stream fileStream = new FileStream(@"d:\UserData.soap",FileMode.OpenOrCreate,FileAccess.Write,FileShare.None)) { formatter.Serialize(fileStream, userData); } using (Stream fileStream = new FileStream(@"d:\UserData.soap",FileMode.Open,FileAccess.Read, FileShare.None)) { UserData data = formatter.Deserialize(fileStream) as UserData; Console.WriteLine(data.ToString()); } }
PS:SoapFormatter在System.Runtime.Serialization.Formatters.Soap命名空间下
运行结果:
a.序列化结果
从红色框可以看出,用户数据被序列化为大写形式
b.反序列化结果
用户数据被反序列化为小写形式
使用特性定制序列化
自.NET2.0发布以来,定制序列化过程的首选方式是定义一些具有新的序列化相关特性的方法(这些特性如[OnSerializing]、[OnSerialized]、[OnDeserializing]、[OnDeserialized])。使用这些特性,减少了实现ISerializable的麻烦,因为不需要手动与输入的SerializationInfo参数交互、二十载格式化程序操作该类型时直接修改状态数据。
下面是我从MSDN上找到的关于序列化特性的介绍。
我将用红色框标注出的两个特性,来实现之前的ISerializable实现的功能。
还有一点需要注意:当应用这些特性时,必须定义方法接收一个StreamingContext参数并返回空(否则,将受到一个运行时异常。)
实现代码
[Serializable] class UserData { private string like; public string Like { get { return like; } set { like = value; } } private string taste; public string Taste { get { return taste; } set { taste = value; } } [OnSerializing] private void OnSerializing(StreamingContext ctx) { //在序列化过程中得到调用 this.Like = this.Like.ToUpper(); this.Taste = this.Taste.ToUpper(); } [OnDeserialized] private void OnDeserialized(StreamingContext ctx) { //反序列化结束后得到调用 this.Like = this.Like.ToLower(); this.Taste = this.Taste.ToLower(); } public override string ToString() { return string.Format("喜好:{0};口味:{1}", Like, Taste); } }
调用的代码和之前的完全一样,这里就不贴出了。大家可以自己去测试。
感谢大家的阅读