设计一个类型时,设计人员必须郑重地决定是否允许类型的实例序列化。类型默认是不可序列化的。例如,以下代码可能不会像你希望的那样工作:
internal struct Point { public Int32 x, y;} private static void OptInSerialization() { Point pt = new Point { x = 1, y = 2 }; using (var stream = new MemoryStream()) { new BinaryFormatter().Serialize(stream, pt);//抛出 SerializationException } }
如果在你的程序中生成并运行上述代码,会发现格式化器的Serialize方法抛出一个异常。问题在于,Point类型的开发人员没有显示的指出Point对象可以序列化。为了解决这个问题,开发人员必须像下面这样向这个类型应用一个名为System.SerializableAttribute的定制的attribute。(注意,这个attribute是在System命名空间中定义的,而不是在System.Runtime.Serialization命名空间中定义的。)
[Serializable] internal struct Point { public Int32 x, y;}
现在,如果重新生成运行应用程序并运行它,就会像预期那样工作,Point对象会顺利序列化到流中。序列化一个对象图时,格式化器会检查每个对象的类型都是可序列化的。如果对象图中的任何一个对象不可序列化,格式化器的Serialize方法都会抛出一个SerializationException异常。
注意:序列化一个对象图时,有的对象的类型也许能序列化,有的也许不能。考虑到性能,在序列化之前,格式化器不会验证对象图中的所有对象都能序列化。所以,序列化一个对象图时,在抛出SerializationException之前,完全有可能已经有一部分对象已经序列化到流中。如果发生这种情况,流中就会包含以损坏的数据。序列化一个对象时。如果你认为也许有一些对象不可序列化,那么写代码就应该能得体的从这些异常中恢复。一个方案是先将对象序列化到MemoryStream中。然后,如果所有对象都成功序列化,就可以将MemoryStream中的字节复制到你真正希望的目标流中(比如文件和网络)。
SerializableAttribute这个定制的attribute只能应用于引用类型(class)、值类型(struct)、枚举类型(enum)和委托(delegate)。注意,枚举和委托类型总是可序列化的,所以不必显示应用SerializableAttribute。除此之外,SerializableAttribute不会被派生继承。所以,给定以下两个类型定义,那么Person对象时可序列化的,而Employee对象时不可序列化的:
[Serializable] class Person { } internal class Employee : Person { }
为了解决这个问题,只要将SerializableAttribute应用于Employee类型:
[Serializable] internal class Person { } [Serializable] internal class Employee : Person { }
注意,上诉问题虽然很容易修正,但反之则不然。如果一个类型没有应用SerializableAttribute,那么很难想象如何从它派生出一个可序列化的类型。但这样设计时有原因的:如果基类不允许他的实例序列化,它的字段就不能序列化,因为基对象实际是派生对象的一部分。这正是为什么System.Object已经很体贴的应用了SerializableAttribute的原因。
注意:通常,建议你定义的大多数类型都设置成可序列化,毕竟,这能为类型的用户提供很大的灵活性。但是必须注意,序列化会读取一个对象的所有字段,不管这些字段声明为public,protected,internal还是private。如果一个类型的实例要包敏感和安全数据,或者数据在转移之后使没有含义或者没有值,就不应该使类型变的可序列化。
如果使用的一个类型不是为序列化而设计的,而且手上没有类型的源代码,无法从源代码添加序列化支持。在后面“反序列化对象时重写程序集和/类型”中,我会解释如何使任何不可序列化的类型变的可序列化。