1、支持绑定到非公有方法的委托
关于这一点,与其说是个增强,在我看来这似乎是设计思路上略微的转变。具体地说,在.NET 1.x,像这样的代码:
[Serializable] class Person { public event EventHandler Birthday; } static void Main(string[] args) { Person p = new Person(); p.Birthday += new EventHandler(p_Birthday); BinaryFormatter bf = new BinaryFormatter(); MemoryStream ms = new MemoryStream(); bf.Serialize(ms, p); } static void p_Birthday(object sender, EventArgs e) { Console.WriteLine("Birthday is coming!"); }
会抛出一个这样的异常:
An unhandled exception of type 'System.Runtime.Serialization.SerializationException' occurred in mscorlib.dll
Additional information: Serialization will not deserialize delegates to non-public methods.
提示信息是很明确的,它不支持绑定到非公有方法(non-public method)的委托。要解决这个问题,你得在p_Birthday方法前加一个public关键字。然而,对于类Person的设计者来说,他不知道事件Birthday将被绑定到什么样的方法之上,所以这带来了一定程度的不确定性。
.NET 2.0已经把这个行为改掉了,这段代码现在可以正常运行。这样,在做序列化/反序列化操作时,这增强了应用程序的稳定性。
2、可选字段增强了不同版本的应用程序之间的互操作性
.NET/FCL 2.0的System.Runtime.Serialization命名空间引入了一个新的名为OptionalFieldAttribute的特性类:
Specifies that a field can be missing from a serialization stream so that the BinaryFormatter and the SoapFormatter does not throw an exception.
This attribute allows you to specify that new fields in a serializable type (a type to which the SerializableAttribute is applied to) are ignored by the BinaryFormatter or the SoapFormatter. This enables version-tolerant serialization of types created for older versions of an application that serializes data. For example, when the formatters encounter a stream produced by a version that does not include the new fields, no exception is thrown, and the existing data on the older type is processed as normal.
这个feature在新/旧版本的应用程序用.NET Remoting进行互操作时特别有用。新版本的类型可能增加了一些新的字段(而老版本没有),这样,对于旧类型的对象通过序列化得到的数据,当你试图通过反序列化生成新类型的对象时,会发生找不到类型成员的错误。现在,你可以用这个OptionalFieldAttribute特性类标记那些新字段,使新/旧版本的应用程序互相兼容。
需要注意的是,要使新/旧版本的应用程序互相兼容,这个OptionalFieldAttribute特性类只是提供了一个approach而已,它不保证忽略(缺少)一些字段后的对象仍然有正常的行为。显而易见,这就像在运行时突然把一个对象的某个私有字段改成null或0值,谁也不知道将会发生什么事情。所以,使用这个特性类时,还需要有设计方面的保证。
然而,要设计这样一个向后兼容的新类型,就算使用非常senior的程序员再加上非常细心的设计也很难保证不出错 — 特别是类型很复杂的情形。对我来说,不到万不得已是不会冒险使用这个OptionalFieldAttribute的。
3、序列化/反序列化事件可以让你更容易地自定义序列化行为
在.NET 1.x,要自定义序列化行为,你得让类型继承ISerializable接口(当然还是要应用[Serializable]特性),实现GetObjectData方法和一个反序列化构造函数,像这样:
[Serializable] class Person : ISerializable { public Person() { } protected Person(SerializationInfo info, StreamingContext context) { } public void GetObjectData(SerializationInfo info, StreamingContext context) { } }
这确实提供了自定义序列化/反序列化行为的途径,你可以在GetObjectData方法中向SerializationInfo手工添加任何数据(哪怕不是对象的成员),然后在反序列化构造函数中从SerializationInfo重新取出数据,再手工初始化对象。
这是一个完备的方案,但是有些情况下它未免有点麻烦。
根据我的experiance,需要自己实现ISerializable接口的情形,往往是因为“大部分字段可以自动序列化(比如int、string),而少数字段不支持序列化(比如Thread、WaitHandle),但这些字段对于对象又是必不可少的”的需求。也就是说,这些不支持序列化的字段在反序列化后仍不允许为空时,你得自己控制序列化/反序列化行为,在反序列化构造函数重新建立它们的实例。
这里的问题是,大部分字段都可以自动序列化的;但是为了少数几个不支持序列化的字段,却需要编写代码为所有字段都进行手工的序列化 — 这些代码不但冗繁,而且容易出错(比如忘掉了处理某个字段)。我一直希望能自己参与自动序列化/反序列化的行为,比如在自动反序列化之后,运行时能自动调用一段我自己的代码,处理那些不支持序列化的字段。
.NET/FCL 2.0满足了我的这个需求,它提供了四个序列化/反序列化事件:OnSerializing、OnSerialized、OnDeserializing、OnDeserialized。它们对应了四个加了Attribute后缀的特性类。这里绑定事件的方法不同于普通.NET事件:事件绑定是通过为方法应用特性来实现的(而不是普通的+=和-=)。不必再详细描述,直接看代码:
[Serializable] class Person : ISerializable { public Person() { } protected Person(SerializationInfo info, StreamingContext context) { Console.WriteLine(".ctor(SerializationInfo, StreamingContext)"); } public void GetObjectData(SerializationInfo info, StreamingContext context) { Console.WriteLine("GetObjectData"); } [OnSerializing] internal void OnSerializing(StreamingContext context) { Console.WriteLine("OnSerializing"); } [OnSerialized] internal void OnSerialized(StreamingContext context) { Console.WriteLine("OnSerialized"); } [OnDeserializing] internal void OnDeserializing(StreamingContext context) { Console.WriteLine("OnDeserializing"); } [OnDeserialized] internal void OnDeserialized(StreamingContext context) { Console.WriteLine("OnDeserialized"); } }
static void Main(string[] args) { BinaryFormatter bf = new BinaryFormatter(); Person p = new Person(); MemoryStream ms = new MemoryStream(); bf.Serialize(ms, p); ms.Position = 0; p = bf.Deserialize(ms) as Person; }
Console输出:
OnSerializing
GetObjectData
OnSerialized
OnDeserializing
.ctor(SerializationInfo, StreamingContext)
OnDeserialized
这里加了ISerializable接口只是为了演示事件发生顺序之用,一般情况下,既然已经使用了这四个事件,那这里是不需要这个ISerializable接口的。