序列化成不同类型
[Serializable] public sealed class Singleton : ISerializable { private static readonly Singleton s_theOneObject = new Singleton(); public string Name = "Oliver"; public DateTime Date = DateTime.Now; private Singleton() { } public static Singleton GetSingleton() { return s_theOneObject; } [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { info.SetType(typeof(SingletonSerializationHelper)); } } [Serializable] public sealed class SingletonSerializationHelper : IObjectReference { public object GetRealObject(StreamingContext context) { return Singleton.GetSingleton(); } }
单例在每个AppDomain只能存在它自己一个实例。上面的例子是,如何序列化/反序列化一个单例。
我们来测试一下上面的代码
static void Main(string[] args) { Singleton[] a1 = { Singleton.GetSingleton(), Singleton.GetSingleton() }; Console.WriteLine(a1[0] == a1[1]); // check reference is the same. using (var stream = new MemoryStream()) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, a1); stream.Position = 0; Singleton[] a2 = (Singleton[])formatter.Deserialize(stream); Console.WriteLine(a2[0] == a2[1]); Console.WriteLine(a1[0] == a2[0]); } }
输出是全是“True”。这说明我们序列化,反序列化出来的对象的实例都是同一个。
现在我们分析一下测试代码来理解一下里面到底发生了什么。
- 首先定义了一个Singleton的数组。在初始化数组时,Singleton里面静态字段先被初始化。Singleton将一个实例存放在私有静态字段m_theOneObject里。然后,静态方法GetSingleton将引用返回,赋值给数组内元素。
- 因为是两个元素的引用相同,故说明两个元素是同一个实例。
- 接下来说明一下using代码段中,序列化与反序列化的过程。序列化第一个数组元素时,格式化器检测到Singleton类型继承自ISerializable接口,并调用了GetObjectData方法。在这个方法中,调用SerializationInfo的SetType方法,向它传递SingletonSerializationHelper类型。GetObjectData方法中只调用了SetType,并没有添加更多信息。同时格式化器自动检测出连个数组元素都引用一个对象,所以格式化器只序列化一个对象。
- 反序列化时,格式化器尝试反序列化一个SingletonSerializationHelper对象,这是格式化器之前被“欺骗”所序列化的东西。(因为这里反序列化一个SingletonSerializationHelper类型的对象,所以并没有为Singleton添加特殊构造函数。)格式化器构造好SingletonSerializationHelper对象后,发现这个类型实现了System.Runtime.Serialization.IObjectReference接口。如果类型实现了这个接口,格式化器会调用GetRealObject方法。这个方法返回在对象反序列化好之后真正想要引用的对象。
- GetRealObject方法返回Singleton的实例。所以最后都将返回true。
这种可以将对象序列化成另外一个类的行为,主要是出于两个方面:
- 允许开发人员序列化最初没有设计成要序列化的一个类型
- 允许开发人员提供一个种方式将类型的一个版本映射到类型的另一个不同的版本
序列化代理
序列化代理就是为了实现序列化成其他类型的一种工作机制。
序列化代理必须实现System.Runtime.Serialization.ISerializationSurrogate接口。
例子:本地的DateTime值,如果你想要在世界其他地方想要用你的DateTime,你如何保证在世界各地看到的时间都是对的呢?
internal sealed class UniversalToLocalTimeSerializationSurrogate : ISerializationSurrogate { public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { info.AddValue("Date", ((DateTime)obj).ToUniversalTime().ToString("u")); } public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) { return DateTime.ParseExact(info.GetString("Date"), "u", null).ToLocalTime(); } }
GetObjectData方法在这里的工作方式与ISerializable接口的GetObjectData方法差不多。唯一的区别在于,ISerializationSurrogate的GetObjectData方法要获取一个额外的参数,要对序列化的”真实“对象的一个引用。
private static void SerializationSurrogateDemo() { using (var stream = new MemoryStream()) {
// 1. Formatter IFormatter formatter = new SoapFormatter(); // 2. Selector SurrogateSelector ss = new SurrogateSelector(); // 3. add DateTime to surrogate ss.AddSurrogate(typeof(DateTime), formatter.Context, new UniversalToLocalTimeSerializationSurrogate());
// AddSurrogate can be added multi times // 4. set selector to surrogate formatter.SurrogateSelector = ss; DateTime localTimeBeforeSerialize = DateTime.Now; formatter.Serialize(stream, localTimeBeforeSerialize); stream.Position = 0; Console.WriteLine(new StreamReader(stream).ReadToEnd()); stream.Position = 0; DateTime localTimeAfterDeserialize = (DateTime)formatter.Deserialize(stream); } }
1-4执行完毕后,格式化器就准备好使用已登记的代理类型。