运行时序列化
摘录于<<clr via c#>> third version
1.概念
序列化:将对象转化成字节流
反序列化:将字节流转化成对象
2.作用
a. asp.net通过序列化和反序列化的方式来保存和恢复会话状态,一个应用程序的状态可以很容易的保存在硬盘上或者数据库中,然后程序下次运行时可以再次从中恢复(检索)
b. 一系列对象可以很容易的被拷贝到系统剪贴板上,然后在同一个或另一个程序里进行粘贴
c. 一系列对象通过网络可以很容易的被传送给运行在另外一台机器上的一个进程,.NET的Remote 技术采用了这种方式
d. 还可以在AppDomain之间传送对对象
e. 只要把对象序列化成字节流后,我们就可以对它做更多有用的处理,如加密、压缩数据等
3.
1 private static MemoryStream SerializeToMemory(Object objectGraph) { 2 // Construct a stream that is to hold the serialized objects 3 MemoryStream stream = new MemoryStream(); 4 5 // Construct a serialization formatter that does all the hard work 6 BinaryFormatter formatter = new BinaryFormatter(); 7 8 // Tell the formatter to serialize the objects into the stream 9 formatter.Serialize(stream, objectGraph); 10 11 // Return the stream of serialized objects back to the caller 12 return stream; 13 }
1 private static Object DeserializeFromMemory(Stream stream) { 2 // Construct a serialization formatter that does all the hard work 3 BinaryFormatter formatter = new BinaryFormatter(); 4 5 // Tell the formatter to deserialize the objects from the stream 6 return formatter.Deserialize(stream); 7 }
.net framework 带有两种序列化方法:BinaryFormatter 和 SoapFormatter,
在.net 3.5里,SoapFormatter已过时,在产品代码中不应该被使用,但在调试序列化代码时,它仍是有用的,我们可以读它产生的XML文本,可以使用XmlSerializer和
DataContractSerializer来进行XML序列化和反序列化
Serialize(stream,object)方法:
第一个参数只要是继承自System.IO.Stream的流类都可以,如MemoryStream, FileStream, NetworkStream等;
第二个参数是一个对象的引用,这个对象可以是一个Int32, a String, a DateTime, an Exception, a List<String>, a Dictionary<Int32,String> and so on;
这个对象里面可能还引用了其他对象,则该对象在序列化时,它引用的所有对象也将一起被序列化;
如果两个对象相互引用,则这两个对象只会被序列化一次,避免无限序列化;
序列化器通过元数据来确定对象所属的类型,通过反射来获取对象中所有的域;
4.在序列化时需要注意的几点
1) 在序列化和反序列时要用相同的序列化器
如:不能在序列化时用SoapFormatter,在反序列化时用BinaryFormatter.
2) 有时将多个对象图序列化为一个流时,可能会很有用,如:
1 [Serializable] 2 private sealed class Customer { /* ... */ } 3 [Serializable] 4 private sealed class Order { /* ... */ } 5 6 private static List<Customer> s_customers = new List<Customer>(); 7 private static List<Order> s_pendingOrders = new List<Order>(); 8 private static List<Order> s_processedOrders = new List<Order>(); 9 10 private static void SaveApplicationState(Stream stream) { 11 // Construct a serialization formatter that does all the hard work 12 BinaryFormatter formatter = new BinaryFormatter(); 13 // Serialize our application’s entire state 14 formatter.Serialize(stream, s_customers); 15 formatter.Serialize(stream, s_pendingOrders); 16 formatter.Serialize(stream, s_processedOrders); 17 } 18 19 private static void RestoreApplicationState(Stream stream) { 20 // Construct a serialization formatter that does all the hard work 21 BinaryFormatter formatter = new BinaryFormatter(); 22 // Deserialize our application’s entire state (same order as serialized) 23 s_customers = (List<Customer>)formatter.Deserialize(stream); 24 s_pendingOrders = (List<Order>)formatter.Deserialize(stream); 25 s_processedOrders = (List<Order>)formatter.Deserialize(stream); 26 }
//代码中的反序列时的顺序要和序列时的顺序一致
3) 在序列化一个对象时,该对象的类型全名以及该类型所在的程序集标识【程序集文件名,版本号,文化,公开的key】都会写入流中;
在反序列化时,首先会解析流中的程序集标识,然后通过调用Assembly's Load方法来确认该程序集是否已经被正解载入当前的AppDomain,在确认后,会在程序集里查找是否有从反序列化中读到的类型,如果没有,就抛异常,对象将不再被反序列化;
如果查找的类型存在,该类型的一个实例将会创建,然后用从流中读到的值为该实例中的各域赋值;
如果该类型中的域和从流中读到域的名字不匹配,也会抛异常,对象将不再被反序列化
5. 使一个对象可序列化
默认的情况下一个对象是不可被序列化的,若要使对象可序列化,得在对象上使用属性[Serializable];
当序列化一组对象时,会检查这组中每个对象是否可被序列化,其中只要有一个是不可被序列化的就会抛出异常;
枚举类型和委托类型总是可以被序列化的,所以没有必要显式使用[Serializable];
子类是不继承父类的可序列化特性的,如:
[Serializable]
internal class Person { }
internal class Employee : Person { }
//Employee是不可被序列化的,如要使Employee类可序列化,则得为Employee加上[Serializable]
基于以上,一般为说,建议将定义的大部分类型标记为可序列化,毕竟,这为使用你定义类型的用户提供了灵活性,
然而有一点需注意,可序列化意味着对象的所有域都将被序列化,无论是public, protected, internal, 或private, 有时可能需要有些域不被序列化,以减少数据量的传输,以提高性能,这时就得对域使用[NonSerialized]属性,该属性只能有在类中的域上,不能对类使用,且子类中该属性仍然有效,即子类型序列化时,子类中继承下来的使用了[NonSerialized]属性的域仍旧是不可被序列化的
6. [NonSerialized]属性
[Serializable]
internal class Circle {
private Double m_radius;
[NonSerialized]
private Double m_area;
public Circle(Double radius) {
m_radius = radius;
m_area = Math.PI * m_radius * m_radius;
}
}
//对m_area使用了[NonSerialized]属性,意味着类Circle在序列化时,m_area是不会被序列化的,这就会产生一个问题,就是在反序列化时,m_area得不到值,它的值为0,解决这一问题的方法是:将为这个域赋值的语句放到一个使用了[OnDeserialized]属性的方法中,如:
[Serializable]
internal class Circle /*: IDeserializationCallback */{
private Double m_radius;
[NonSerialized]
private Double m_area;
public Circle(Double radius) {
m_radius = radius;
m_area = Math.PI * m_radius * m_radius;
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context) {
m_area = Math.PI * m_radius * m_radius;
}
}
//无论何时,只要在一个类被反序列化时,序列化器会检查这个类中是否有定义带 [OnDeserialized]属性的方法,如果有,序列化器就会调用它,在调用之后,类中不可被序列化的域也会得到正确的值
7.序列化器是怎么序列化一个对象中的域
序列化:
1)先调用FormatterServices's GetSerializableMembers方法,该方法原型为:
public static MemberInfo[] GetSerializableMembers(Type type, StreamingContext context);
该方法利用反射来获取类型中的public和private域,除了那些标有[NonSerialized]属性的域外,最后以数组的形式返回所有获取到的域
2)序列化对象,并把1)中获得的MemberInfo[]数组传给FormatterServices's static GetObjectData method,原型为:
public static object[] GetObjectData(object obj, MemberInfo[] members);
该方法作用是将传进来的 MemberInfo[] members数组中的每个域所对应的值(value)放到object[]数组中,这两个数组中的元素是一一对应的,即MemberInfo[0]中的域所对应的值为object[0]。
3)把程序集标识以及类型的全名写入流中
4)枚举MemberInfo[]和object[],把域名和它所对应的值写入流中
反序列化:
1)首先读取程序集信息以及类型全名,如果程序集没有被正确载入,就会抛出异常,对象就不能被反序列化,如果程序集已被正确载入当前的AppDomain,接着会把程序集以及类型全名传递给FormatterServices's 静态方法GetTypeFromAssembly,原型为:
public static Type GetTypeFromAssembly(Assembly assem, string name);
返回正在被序列化对象的类型
2)接着调用FormatterServices's 静态方法GetUninitializedObject,原型为:
public static object GetUninitializedObject(Type type);
把获取的类型传递给该方法,为一个新对象分配内存空间,但不会调用对象的构造函数
3)调用FormatterServices's GetSerializableMembers方法,构造并初始化MemberInfo[]数组,就是把被序列化的域放入这个数组中
4)调用FormatterServices's GetObjectData方法,构造并初始化object[]数组,就是把各域所对应的值一一对应放入这个数组中
5)最后把上面这两个数组传递给FormatterServices's PopulateObjectMembers方法,
public static object PopulateObjectMembers(object obj, MemberInfo[] members, object[] data);
该方法枚举这两个数组,为各域赋值,到这里,对象就被完全反序列化了。