第 24 章 运行时序列化
第 24 章 运行时序列化
NyxX第 24 章 运行时序列化
本章内容
- 序列化/反序列化快速入门
- 使类型可序列化
- 控制序列化和反序列化
- 格式化器如何序列化类型实例
- 控制序列化/反序列化的数据
- 流上下文
- 将类型序列化为不同的类型以及将对象反序列化为不同的对象
- 序列化代理
- 反序列化对象时重写程序集和/或类型
序列化是将对象或对象图转换成字节流的过程。反序列化是将字节流转换回对象图的过程。在对象和字节流之间转换是很有用的机制。下面是一些例子。
24.1 序列化/反序列化快速入门
格式化器参考对每个对象的类型进行描述的元数据,从而了解如何序列化完整的对象图。序列化时,Serialize 方法利用反射来查看每个对象的类型中都有哪些实例字段。在这些字段中,任何一个引用了其他对象,格式化器的 Serialize 方法就知道那些对象也要进行序列化。如果对象图中的两个对象相互引用,格式化器会检测到这一点,每个对象都只序列化一次,避免发生死循环。
面是一个有趣而实用的方法,它利用序列化创建对象的深拷贝(或者说克隆体):
1 | private static Object DeepClone(Object original) { |
序列化对象时,类型的全名和类型定义程序集的全名会被写入流。反序列化对象时,格式化器首先获取程序集标识信息。并通过调用 System.Refleciton.Assembly 的 Load方法,确保程序集已加载到正在执行的 AppDomain 中。
24.2 使类型可序列化
开发者必须向类型应用定制特性 System.SerializableAttribute
1 | [] |
SerializableAttribute 这个定制特性只能应用于引用类型(class)、值类型(struct)、枚举类型(enum)和委托类型(delegate)。注意,枚举和委托类型总是可序列化的,所以不必显式应用 SerializableAttribute 特性。除此之外,SerializableAttribute 特性不会被派生类型继承。
24.3 控制序列化和反序列化
将 SerializableAttribute 定制特性应用于类型,所有实例字段(public,private 和 protected等)都会被序列化①。但类型可能定义了一些不应序列化的实例字段。一般有两个原因造成我们不想序列化部分实例字段。
① 在标记了 [Serializable] 特性的类型中,不要用 C#的“自动实现的属性”功能来定义属性。这是由于字段名是由编译器自动生成的,而生成的名称每次重新编译代码时都不同。这会阻止类型被反序列化。
- 字段含有反序列化后变得无效的信息。例如,假定对象包含 Windows 内核对象(
- 字段含有很容易计算的信息。
以下代码使用 System.NonSerializedAttribute 定制特性指出类型中不应序列化的字段。
1 | [] |
注意,该特性只能应用于类型中的字段,而且会被派生类型继承。
当流反序列化成 Circle 对象的 m_radius 字段会被设为 10,但它的 m_area 字段会被初始化成 0
1 | [] |
修改过的 Circle 类包含一个标记了 System.Runtime.Serialization.OnDeserializedAttribute 定制特性的方法。每次反序列化类型的实例,格式化器都会检查类型中是否定义了应用了该特性的方法。如果是,就调用该方法。调用这个方法时,所有可序列化的字段都会被正确设置。在该方法中,可能需要访问这些字段来执行一些额外的工作,从而确保对象的完全反序列化。
除了 OnDeserializedAttribute 这个定制特性,System.Runtime.Serialization 命名空间还定义了包括 OnSerializingAttribute,OnSerializedAttribute 和 OnDeserializingAttribute 在内的其他定制特性。可将它们应用于类型中定义的方法,对序列化和反序列化过程进行更多的控制。在下面这个类中,这些特性被应用于不同的方法:
1 | [] |
类型中新增的每个字段都要应用 OptionalFieldAttribute 特性。然后,当格式化器看到该特性应用于一个字段时,就不会因为流中的数据不包含这个字段而抛出 SerializationException.
24.4 格式化器如何序列化类型实例
以下步骤描述了格式化器如何自动序列化类型应用了 SerializableAttribute特性的对象。
格式化器调用
FormatterServices的GetSerializableMembers方法:public static MemberInfo[] GetSerializableMembers(Type type, StreamingContext context);
这个方法利用反射获取类型的public和private实例字段(标记了NonSerializedAttribute特性的字段除外)。方法返回由MemberInfo对象构成的数组,其中每个元素都对应一个可序列化的实例字段。对象被序列化,
System.Reflection.MemberInfo对象数组传给FormatterServices的静态方法GetObjectData:public static Object[] GetObjectData(Object obj, MemberInfo[] members);
这个方法返回一个Object数组,其中每个元素都标识了被序列化的那个对象中的一个字段的值。这个Object数组和MemberInfo数组是并行(parallel)的;换言之,Object数组中元素 0 是MemberInfo数组中的元素 0 所标识的那个成员的值。格式化器将程序集标识和类型的完整名称写入流中。
格式化器然后遍历两个数组中的元素,将每个成员的名称和值写入流中。
以下步骤描述了格式化器如何自动反序列化类型应用了 SerializableAttribute 特性的对象。
格式化器从流中读取程序集标识和完整类型名称。如果程序集当前没有加载到 AppDomain 中,就加载它。如果程序集已加载,格式化器将程序集标识信息和类型全名传给
FormatterServices的静态方法GetTypeFromAssembly:
public static Type GetTypeFromAssembly(Assembly assem, String name);
这个方法返回一个System.Type对象,它代表要反序列化的那个对象的类型。格式化器调用
FormmatterServices的静态方法GetUninitializedObject:public static Object GetUninitializedObject(Type type);
这个方法为一个新对象分配内存,但不为对象调用构造器。然而,对象的所有字节都被初始为null或0。格式化器现在构造并初始化一个
MemberInfo数组,具体做法和前面一样,都是调用FormatterServices的GetSerializableMembers方法。这个方法返回序列化好、现在需要反序列化的一组字段。格式化器根据流中包含的数据创建并初始化一个
Object数组。将新分配对象、
MemberInfo数组以及并行Object数组(其中包含字段值)的引用传给FormatterServices的静态方法PopulateObjectMembers:
public static Object PopulateObjectMembers(Object obj, MemberInfo[] members, Object[] data);
这个方法遍历数组,将每个字段初始化成对应的值。到此为止,对象就算是被彻底反序列化了。





