使用SerializableAttribute这个定制attribute应用于一个类型时,所有实例字段都会被序列化。然而,类型可能定义了一些不应序列化的实例字段。一般情况下,有两个原因促使我们不想对类型的部分实例字段进行序列化。
- 字段含有反序列化变得无效的信息。例如,假定一个对象包含到一个Window内核对象(如文件、进程、线程、互斥体、事件、信号量等)的句柄,那么在序列化到另一个进程或另一台机器之后,就会失去意义。因为Windows内核对象时跟进程相关的值。
- 字段含有很容易计算的信息。在这种情况下,要选出哪些无需序列化的字段,减少需要传输的数据,从而增强应用程序的性能。
以下代码使用了NonSerializableAttribute定制的attribute来指明类型的哪些字段不应序列化。注意,这个attribute也是在System命名空间中定义的。
[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; } }
在上述代码中,Circle的对象可以序列化。然而,格式化器只会序列化对象的m_radius字段的值。m_area字段中的值不会序列化,因为字段已应用了NonSerializableAttribute。注意,这个attribute只能应用于类型中的一个字段,而且会被派生类继承。当然,可以向一个类型中的多个字段应用NonSerializableAttribute。
假定我们的代码像下面这样构造了一个Circle对象:
Circle c=new Circle(10);
在内部,m_area字段会设置成一个约为314.159的值。这个对象序列化时,只有m_radius字段的值才会写入流。这正是我们希望的,但是当流反序列化一个Circle对象时,就会遇到一个问题。反序列化时,Circle对象的m_radius字段会被设为10,但它的m_area字段会被初始化成0,而不是314.159!
以下代码演示了如何修改Circle类型来修正这个问题:
[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; } [OnDeserialized] private void OnDeserialized(StreamingContext context) { m_area = Math.PI * m_radius * m_radius; } }
在修改过的Circle中,包含一个用System.Runtime.Serialization.OnDeserializedAttribute定制的attribute进行了标记的方法。每次反序列化一个类型的实例,格式化器都会检查类型中是否定义了一个应用了该attribute的方法。如果是,就调用该方法。调用这个方法时,所有可序列化的字段都会被正确设置。在方法中,可能需要访问这些字段来执行一些额外的工作,从而确保对象的完全发序列化。
在上述Circle修改版本中,我调用OnDeserialized方法,使用m_radius字段来计算圆的面积,并将结果放到m_area字段中。这样一来,m_area就有了我们希望的值.
除了OnDeserializedAttribute这个定制attrubite,System.Runtime.Serialization命名空间中还定义了OnSerializingAttribute,OnSerializingAttribute和OnDeserializedAttribute这些定制attribute.可将他们应用于类型中定义的方法,对反序列化和序列化过程进行更多的控制。在下面这个类中,这些attribute被应用于不同的方法:
[Serializable] public class MyType { Int32 x, y; [NonSerialized] Int32 sum; public MyType(Int32 x, Int32 y) { this.x = x; this.y = y; sum = x + y; } [OnDeserializing] private void OnDeserializing(StreamingContext context) { //示例:在这个类型的新版本中,为字段设置默认值 } [OnDeserialized] private void OnDeserialized(StreamingContext context) { //示例:根据字段初始化瞬时状态 sum = x + y; } [OnSerializing] private void OnSerializing(StreamingContext context) { //示例:在序列化钱修改任何需要修改的状态 } [OnSerialized] private void OnSerialized(StreamingContext context) { //示例:在序列化后,恢复任何需要恢复的状态 } }
使用这4个属性中的任何一个时,你定义的方法必须获取一个StreamingContext参数并返回void.方法名可以使你希望的任何名称。另外,应该将方法声明为private,以避免他被普通的代码调用;格式化器运行时有充足的的权限,所以能调用私有方法。
如果序列化一个类型的实例,在类型中添加一个新字段,然后试图反序列化不包含新字段的对象,格式化器会抛出一个SerializationException异常,并显示一条消息告诉你流中要反序列化的数据包含错误的成员数目。这非常不利于版本控制,因为我们经常要在一个类型的版本中添加字段。幸好,这是可以利用System.Runtime.Serialization.OptionalFieldAttribute的帮助。
类型中新增的每个字段都应用一个OptionalFieldAttribute,然后,当格式化器遇到该attribute应用于一个字段时,就不会因为流中的数据不包含这个字段而抛出SerializationException。
注意 序列化一组对象时,格式化器首先调用对象标记了OnSerializing attribute的所有方法。接着,它序列化所有对象的字段。最后,调用对象标记了OnSerialized的所有方法。类似的,反序列化一组对象时,格式化器调用对象标记了OnDeserializing所有方法,然后,它反序列对象的所有字段。最后,它调用对象的标记了OnDeserialized的所有方法。
还要注意,在反序列化期间,当一个格式化器看到一个类型提供的一个方法标记了OnDeserialized时,格式化器会将这个对象的引用添加到一个内部列表中。所有对象都反序列化之后,格式化器以相反的方向遍历这个列别,调用每个对象的OnDeserialized方法。调用这个方法后,所有可序列化的字段都会被正确设置,可访问这些字段来执行任何必要的、进一步的工作,以便将对象完整的反序列化。之所以要以相反的顺序调用这些方法,因为允许内层对象先于外层对象结束反序列化。
例如,假定一个集合对象内部用一个哈希表维护他的数据项列表。集合对象类型可实现一个标记了OnDeserialized方法。即使集合对象先反序列化,它的OnDeserialized方法也会最后调用(在调用完它的数据项的所有OnDeserialized方法之后)。这样一来,所有数据项都反序列化后,他们的所有字段都能得到正确的初始化,以便计算出一个好的哈希码值。然后,集合对象创建它的内部哈希桶、并利用数据项的哈希码将数据项放到桶中。