一、什么是序列化
.net的运行时环境用来支持用户定义类型的流化的机制。它是将对象实例的状态存储到存储媒体的过程。在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字节流写入数据流。在随后对对象进行反序列化时,将创建出与原对象完全相同的副本。
二、序列化的目的
1. 持久化对象,用于存储。
2. 传输对象。
三、序列化与反序列化的实现
IFormatter
实质上序列化机制是将类的值转化为一个一般的(即连续的)字节流,然后就可以将该流写到磁盘文件或任何其他流化目标上。而要想实际的写出这个流,就要使用那些实现了IFormatter接口的类里的Serialize和Deserialize方法。
BinaryFormatter
BinaryFormatter使用二进制格式化程序进行序列化。您只需创建一个要使用的流和格式化程序的实例,然后调用格式化程序的 Serialize 方法。流和要序列化的对象实例作为参数提供给此调用。类中的所有成员变量(甚至标记为 private 的变量)都将被序列化。
[Serializable] //表明此类可以序列化,而[NonSerializable]表明不可序列化
public class MyObject {
public int a;
private string b;
public string B {
get {
return b;
}
set {
b = value;
}
}
}
public static void Main() {
MyObject obj = new MyObject();
obj.a = 1;
obj.B = "my object instance";
//序列化obj
IFormatter formatter = new BinaryFormatter();
FileStream stream = new FileStream("MyFile.bin", FileMode.Create,
FileAccess.Write, FileShare.None);
formatter.Serialize(stream, obj);
stream.Close();
//反序列化
FileStream desStream = new FileStream("MyFile.bin", FileMode.Open,
FileAccess.Read, FileShare.Read);
MyObject desObj = (MyObject)formatter.Deserialize(desStream);
desStream.Close();
Console.WriteLine("desObj.a: {0}/r/ndesObj.b: {1}", desObj.a, desObj.B);
}
SoapFormatter
SoapFormatter使用XML格式化程序进行序列化,用法同上。
Serializable属性
此属性不能继承,要想让派生类可以序列化,则必须在派生类中再次用Serializable属性标记一次。
自定义序列化
如果你希望让用户对类实现序列化,但是对数据流的组织方式不完全满意,那么可以通过在对象上实现 ISerializable 接口来自定义序列化过程。这一功能在反序列化后成员变量的值失效时尤其有用,但是需要为变量提供值以重建对象的完整状态。除了必须将类申明为 Serializable 的同时,还要要实现 ISerializable接口,需要实现 GetObjectData 方法以及一个特殊的构造函数,在反序列化对象时要用到此构造函数。在实现 GetObjectData 方法时,最常调用的SerializationInfo的方法是AddValue,这个方法具有针对所有标准类型(int、char等等)的重载版本;而 StreamingContext 参数描述给定的序列化流的源和目标,这样我们就可以知道我们是将对象序列化到持久性存储还是在将他们跨进程或机器序列化。而在反序列化时,我们调用SerializationInfo提供的一组Getxxx方法,他们针对所有标准类型数据执行各种AddValue重载版本的逆操作。下代码示例说明了如何在前一部分中提到的 MyObject 类上实现 ISerializable。
[Serializable]
public class MyObject : ISerializable {
public int a;
private string b;
public string B {
get {
return b;
}
set {
b = value;
}
}
public MyObject() {}
//特殊的构造函数,必须实现,但是如果未实现,编译器不提出警告
protected MyObject(SerializationInfo info,
StreamingContext context) {
a = info.GetInt32("a");
b = info.GetString("b");
}
public virtual void GetObjectData(SerializationInfo info,
StreamingContext context) {
info.AddValue("a", a);
info.AddValue("b", b);
}
}
在序列化过程中调用 GetObjectData 时,需要填充方法调用中提供的 SerializationInfo 对象。只需按“名称/值”对的形式添加将要序列化的变量。其名称可以是任何文本。只要已序列化的数据足以在反序列化过程中还原对象,便可以自由选择添加至 SerializationInfo 的成员变量。如果基类实现了 ISerializable,则派生类应调用其基类的 GetObjectData 方法。
需要强调的是,将 ISerializable 添加至某个类时,需要同时实现 GetObjectData 以及特殊的具有特定原型的构造函数——重要的是,该构造函数的参数列表必须与GetObjectData相同,这个构造函数将会在反序列化的过程中使用:格式化器从流中反序列化数据,然后通过这个构造函数生成实例。如果缺少 GetObjectData,编译器将发出警告。但是,由于无法强制实现构造函数,所以,缺少构造函数时不会发出警告。如果在没有构造函数的情况下尝试反序列化某个类,将会出现异常。注释:在消除潜在安全性和版本控制问题等方面,当前设计优于 SetObjectData 方法。例如,如果将 SetObjectData 方法定义为某个接口的一部分,则此方法必须是公共方法,这使得用户不得不编写代码来防止多次调用 SetObjectData 方法。可以想象,如果某个对象正在执行某些操作,而某个恶意应用程序却调用此对象的 SetObjectData 方法,将会引起一些潜在的麻烦。
在反序列化过程中,使用出于此目的而提供的构造函数将 SerializationInfo 传递给类。对象反序列化时,对构造函数的任何可见性约束都将被忽略,因此,在类未封装(声明为Sealed)的情况下,将此特殊构造函数声明为 protected。如果类已封装,则应标记为 private。要还原对象的状态,只需使用序列化时采用的名称,从 SerializationInfo 中检索变量的值。如果基类实现了 ISerializable,则应调用基类的构造函数,以使基类的变量可以还原其值。
如果从实现了 ISerializable 的类派生出一个新的类,则只要新的类中含有任何需要序列化的变量,就必须同时实现构造函数以及覆盖GetObjectData 方法。以下代码片段显示了如何使用上文所示的 MyObject 类来完成此操作。
[Serializable]
public class ObjectTwo : MyObject {
public int num;
public ObjectTwo() : base(){ }
protected ObjectTwo(SerializationInfo info,
StreamingContext context) : base(si,context) {
num = si.GetInt32("num");
}
public override void GetObjectData(SerializationInfo info,
StreamingContext context) {
base.GetObjectData(info,context);
si.AddValue("num", num);
}
}
切记要在反序列化构造函数中调用基类构造函数,否则,将永远不会调用基类上的构造函数,并且在反序列化后也无法构建完整的对象。
对象被彻底重新构建,但是在反系列化过程中调用方法可能会带来不良的副作用,因为被调用的方法可能引用了在调用时尚未反序列化的对象引用。如果正在进行反序列化的类实现了 IDeserializationCallback,则反序列化整个对象图表后,将自动调用 OnSerialization 方法。此时,引用的所有子对象均已完全还原。有些类不使用上述事件侦听器,很难对它们进行反序列化,散列表(Hashtable)便是一个典型的例子。在反序列化过程中检索关键字/值对非常容易,但是,由于无法保证从散列表派生出的类已反序列化,所以把这些对象添加回散列表时会出现一些问题。因此,建议目前不要在散列表上调用方法。
.NET的序列化简介-
最新推荐文章于 2024-08-16 13:10:40 发布