前面讨论了如何修改一个类型的实现,控制该类型如何对他本身的实现进行序列化和反序列化。然而,格式化器还允许不是”类型实现的一部分”的代码重写该类型序列化和反序列化器对象的方式。应用程序之所以要重写类型的的行为,主要是基于两个方面的考虑。
1、 允许开发人员序列化最初没有设计成要序列化的一个类型。
2、 允许开发人员提供一种方式将类型的一个版本映射到类型的一个不同的版本。
简单的说,为了使这个机制工作起来,首先需要定义一个”代理类型”,它接受对现有类型类型进行序列化和反序列化的行动。然后,向格式化器登记该代理类型的一个实例,告诉格式化器代理类型要作用于现有的哪一个类型。格式化器检测到它正要对现在类型的一个实例进行序列化和反序列化时,会调用由你的代理对象定义的方法。让我们通过一个例子来演示这一切时如何工作的。
序列化代理类型必须实现System.Runtime.Serialization.ISerializationSurrogate接口,它在FCL中是像下面这样定义的:
public interface ISerializationSurrogate
{
void GetObjectData(object obj, SerializationInfo info, StreamingContext context);
object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector);
}
让我们分析使用了该接口的一个例子。假定程序含有一些DateTime对象,其中包含用户计算机的本地值。如果想把DateTime对象序列化到一个流中,同时希望值用国际标准时间序列化,那么应该如何操作呢?这样一来,就可以经数据通过网络流发送给世界上其他地方的另一台计算机,使Datetime值保持正确。虽然不能修改FCL自带的DateTime类型,但可以定义自己的序列化代理类,它能控制Datetime对象的序列化和反序列化方式,
sealed class UniversalToLocalTimerSerializationSurrogate : 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方法要获取一个额外的参数:对要序列化真实对象的一个引用。在上诉GetObjectData方法中,这个对象转型为DateTime,值从本地时间转型为世界时,并将一个字符串添加到SerializationInfo集合。
SetObjectData方法用于反序列化一个DateTime对象。调用这个方法时,要想它传递一个SerializationInfo对象的引用。SetObjectData从这个结合中获取字符串形式的日期,把它解析成一个通用完整日期时间模式的字符串,然后将结果DateTime对象从世界时转换成计算机的本地时间。
传给SetObjectData的第一个参数的Object有一点奇怪。在调用SetObjectData之前,格式化器分配(通过FormatterServices.GetUninitializedObject)要代理的那个类型的一个实例。实例字段全是0/null,而且没有在对象上调用构造器。SetObjectData内部的代码为了初始化这个实例的字段,可以使用传入的SerializationInfo中的值,并让SetObjectData返回null.
另外,SetObjectData可以创建一个完全不同的对象,甚至创建不同类型的一个对象,并返回新对象的一个引用。在这种情况下,不管传给SetObjectData的对象有发生更改,格式化器都会忽略。
在我的例子中,我的UniversalToLocalTimerSerializationSurrogate类扮演了DateTime类型的代理角色。DateTime的一个值类型,所以obj参数引用一个DateTime的一个已装箱的实例。大多数值类型中的字段都无法更改,所以我的SetObjectData方法会忽略obj参数,并返回一个新的DateTime对象,其中以装好了希望的值。
这时,你肯定想问,尝试序列化和反序列化一个DateTime对象时,格式化器怎么知道要用这个ISerializationSurrogate类型呢?以下代码对UniversalToLocalTimerSerializationSurrogate类进行测试:
private static void SerializationSurrogateDemo() { using (var stream = new MemoryStream()) { //1.构造所需的格式化器 IFormatter formatter = new SoapFormatter(); //2.构造一个SurrogateSelector对象,代理选择器 SurrogateSelector ss = new SurrogateSelector(); //3.告诉代理选择器为DateTime对象使用我们的代理 ss.AddSurrogate(typeof(DateTime), formatter.Context, new UniversalToLocalTimerSerializationSurrogate()); // 4.告诉格式化器使用代理选择器 formatter.SurrogateSelector = ss; //创建datetime类来代表机器上的本地使用时间。 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); Console.WriteLine("localTimeBeforeSerialize={0}" + localTimeBeforeSerialize); Console.WriteLine("localTimeAfterDeserialize={1}" + localTimeAfterDeserialize); } }
步骤1到4执行完毕后,格式化器就准备好使用已登记的代理类型,调用格式化器的Serialize方法时,会在SurrogateSelector维护的集合中查找每个对象的类型。如果发现一个匹配,就调用ISerializationSurrogate对象的GetObjectData方法来获取应该写入流的信息。
格式化器的Deserialize方法被调用时,会在SurrogateSelector中查找要反序列化的类型。如果发现一个匹配,就调用ISerializationSurrogate的SetObjectData方法来设置要反序列化的对象中的字段。
SurrogateSelector对象在内部维护一个私有的哈希表。调用AddSurrogate时,Type和StreamingContext构成了哈希表的key,对象的值就是ISerializationSurrogate对象,如果已经存在一个相同的key,AddSurrogate会抛出一个ArgumentException。通过在key中包含StreamingContext,可以登记一个代理类型的对象,它知道如何将DateTime对象序列化和反序列化到一个文件中;在登记一个不同的代理对象,它知道如何将DateTime对象序列化和反序列化到一个不同的进程中。
代理选择链
多个SurrogateSelector对象可连接在一起。例如,可以让一个SurrogateSelector对象维护一组序列化代理,这些序列化代理用于将类型序列化成代理,以便通过网络传输,或者跨越不同的AppDomain传输。还可以让另一个SurrogateSelector对象维护一组系列化代理,这些序列化代理将版本1类型转换成版本2类型。
如果有多个希望格式化器使用的SurrogateSelector对象,必须把他们连接到一个链表中,SurrogateSelector类型实现了ISurrogateSelector接口,该接口定义了三个方法。这些方法全部跟连接有关。
public interface ISurrogateSelector
{
void ChainSelector(ISurrogateSelector selector);
ISurrogateSelector GetNextSelector();
ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector);
}
ChainSelector方法紧接着当前操作ISurrogateSelector对象之后插入一个ISurrogateSelector对象。GetNextSelector方法返回对链表中下一个ISurrogateSelector对象的引用;如果当前操作的对象时尾部,就返回null.
GetSurrogate方法在this所代表的ISurrogateSelector对象中查找一对Type/StreamingContext。如果没有找到Type/StreamingContext对,就访问链表的下一个ISurrogateSelector对象,以此类推。如果找到一个匹配项,GetSurrogate方法就返回ISerializationSurrogate对象,该对象负责对找到的类型进行序列化和反序列化。除此之外,GetSurrogate方法返回包含匹配的ISurrogateSelector对象;一般都用不着这个对象,所以一般都将其忽略。如果链中所有ISurrogateSelector对象都不包含一对匹配的Type/StreamingContext,就返回null.