1. 如何将某类型的对象序列化成另一个类型的数据流?
2. 如何将某类型的数据流反序列化成另一个类型的对象?
下面列举几个场景,会遇到上面的两个问题:
1. 单实例类型(singleton),对于这种类型对象的序列化和反序列不应该在AppDomain中创建新的对象,应该使用已经存在的单实例对象。
2. 对于远程控制的对象,CLR序列化服务器端对象有关的信息,并通过网络传输给客户端,在客户端反序列化的时候,会创建一个本地代理对象,这个代理对象的类型不同于服务器端对象的类型。但是这对于客户端代码来说是透明的,客户端直接使用本地的代理对象,代理对象内部会请求远程服务器,由服务器端实际执行具体的操作。
单实例类型代码演示:
[Serializable]
public class Singleton : ISerializable
{
private static readonly Singleton _singleton = new Singleton();
public string Name = "Nestor";
public DateTime Date = DateTime.Now;
private Singleton() { }
//注意:特殊构造器是不必要的,因为它永远不会调用到
public static Singleton GetSingleton() { return _singleton; }
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.SetType(typeof(SingletonSerializationHelper));
}
[Serializable]
private sealed class SingletonSerializationHelper : IObjectReference
{
object IObjectReference.GetRealObject(StreamingContext context)
{
return Singleton.GetSingleton();
}
}
}
Singleton[] singletons = { Singleton.GetSingleton(), Singleton.GetSingleton() };
// TRUE
bool isSame1 = object.ReferenceEquals(singletons[0], singletons[1]);
using (var stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, singletons);
stream.Position = 0;
Singleton[] singletons2 = (Singleton[])formatter.Deserialize(stream);
// TRUE
bool isSame2 = object.ReferenceEquals(singletons2[0], singletons2[1]);
// TRUE
bool isSame3 = object.ReferenceEquals(singletons[0], singletons2[1]);
}
对上面的代码进行总结:
1. Singleton类型加载到AppDomain时,CLR会调用它的静态构造器初始化一个Singleton对象,并保存在_singleton字段里。
2. Singleton类型没有提供任何公共构造器,防止其他地方构造多个实例。
3. 创建Singleton[]数组,每个元素都调用GetSingleton()方法,返回单Singleton对象的引用。
序列化部分:
4. 格式化器的Serialize方法,在序列化的时候检测到Singleton类型实现了ISerializable接口,就会调用接口的GetObjectData方法。
5. GetObjectData方法里调用了SetType方法,是告诉格式化器将Singleton对象序列化到SingletonSerializationHelper对象。
6. 格式化器检测出数组中两个元素都引用同一个对象,格式化器只会序列化一个对象。
反序列化部分:
7. 格式化器的Deserialize方法,对数据流进行反序列时,数据流中保存的类型是SingletonSerializationHelper,所以格式化器尝试反序列化一个SingletonSerializationHelper对象。
8. 在构造这个对象后,格式化器会检查对象的类型是否实现了IObjectReference接口,如果对象的类型实现了IObjectReference接口,格式化器会调用接口里的GetRealObject方法,这个方法会返回反序列好之后真正的类型的对象。
IObjectReference接口定义如下:
public interface IObjectReference
{
/// <summary>Returns the real object that should be deserialized, rather than the object that the serialized stream specifies.</summary>
/// <returns>Returns the actual object that is put into the graph.</returns>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> from which the current object is deserialized. </param>
/// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission. The call will not work on a medium trusted server.</exception>
[SecurityCritical]
object GetRealObject(StreamingContext context);
}
通过上面一步步分析代码,我们知道:
1. 序列化到不同类型,通过GetObjectData方法里调用了SetType方法做到。
2. 反序列到不同类型,通过对象的类型是否实现了IObjectReference接口来完成。
序列化到不同类型代码演示:
[Serializable]
class MyClass1 : ISerializable
{
public int MyProperty1;
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.SetType(typeof(MyClass2));
info.AddValue("MyProperty1", this.MyProperty1);
}
}
[Serializable]
class MyClass2 : ISerializable
{
public int MyProperty2;
private MyClass2(SerializationInfo info, StreamingContext context)
{
this.MyProperty2 = info.GetInt32("MyProperty1");
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
throw new NotImplementedException();
}
}
MyClass1 my = new MyClass1() { MyProperty1 = 1 };
using (var stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, my);
stream.Position = 0;
MyClass2 my2 = (MyClass2)formatter.Deserialize(stream);
}
总结上面的代码,MyClass1对象序列化到MyClass2类型的数据流,MyClass2类型的数据流反序列到MyClass2类型的对象。
因为MyClass1不会被反序列化,所以不用提供特殊构造器。
MyClass2不会被序列化,所以不用提供ISerializable.GetObjectData方法的实现。
反序列到不同类型代码演示:
[Serializable]
class MyClass1 : ISerializable, IObjectReference
{
public int MyProperty1;
public MyClass1() { }
private MyClass1(SerializationInfo info, StreamingContext context)
{
this.MyProperty1 = info.GetInt32("MyProperty1");
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("MyProperty1", this.MyProperty1);
}
object IObjectReference.GetRealObject(StreamingContext context)
{
return new MyClass2() { MyProperty2 = this.MyProperty1 };
}
}
class MyClass2
{
public int MyProperty2;
}
MyClass1 my = new MyClass1() { MyProperty1 = 1 };
using (var stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, my);
stream.Position = 0;
MyClass2 my2 = (MyClass2)formatter.Deserialize(stream);
}
总结上面的代码,MyClass1对象序列化到MyClass1类型的数据流,MyClass1类型的数据流反序列到MyClass2类型的对象。
主要通过检查反序列化出来的对象的类型是否实现了IObjectReference接口,如果实现该接口会做一次GetRealObject方法的调用。
FCL的逻辑如下:
if (this.TopObject is IObjectReference)
{
this.TopObject = ((IObjectReference)this.TopObject).GetRealObject(this.m_context);
}
总结全文,主要讨论以下两个问题:
1. 如何将某类型的对象序列化成另一个类型的数据流?
2. 如何将某类型的数据流反序列化成另一个类型的对象?
答案是:
1. 序列化到不同类型,通过GetObjectData方法里调用了SetType方法做到。
2. 反序列到不同类型,通过对象的类型是否实现了IObjectReference接口来完成。
-纸上得来终觉浅,绝知此事要躬行-