特殊声明:如下所示:
1.本博客中的"特殊构造函数"指的是具有SerializationInfo和StreamingContext类型参数的构造函数。
基础知识:如下所示:
1.对象图是一个抽象的概念,代表的是对象系统在特定时间点的一个视图。
2.序列化是将对象图转换成字节流的过程。
3.反序列化是将字节流转换成对象图的过程。
4.XmlSerializer和DataContractSerializer类可以将XML进行序列化和反序列化。
5.BinaryFormatter格式化器可以将对象图进行序列化和反序列化。
6.可以将多个对象图序列化到一个字节流中,然后从该字节流中反序列化成多个对象图。
7.进行序列化操作时,当前操作的对象不能被序列化时,就会抛出SerializationException。
8.SerializationInfo类的SetType函数用来设置真实要序列化的对象。
9.类型实现了IObjectReference的GetRealObject函数时,该函数会在反序列化字节流后才被调用,从而返回真实的类型对象。
10.代理选择器链是将代理选择器对象链接在一起的技术。代理选择器对象可以通过ISurrogateSelector.ChainSelector函数来将新的代理选择器对象添加到链尾;通过ISurrogateSelector.GetNextSelector函数来获取下一个代理选择器对象;通过ISurrogateSelector.GetSurrogate函数来获取指定类型引用对象和流上下文对象的代理选择器对象。
格式化器以反射的方式序列化对象:如下所示:
1.类型以及该类型关联的基类型都必须应用SerializableAttribute定制特性。
2.类型的字段应用NonSerializedAttribute定制特性时,该字段才可以不被序列化。
3.类型中的函数应用OnSerializingAttribute定制特性时,该函数会在序列化对象前调用。
4.执行序列化对象的流程如下所示:
1>.调用FormatterServices的GetSerializableMembers函数来获取MemberInfo数组(可序列化字段信息数组)。
2>.调用FormatterServices的GetObjectData函数来获取Object数组(可序列化字段数值数组)。
3>.将程序集标识和类型的完整名称写入字节流中。
4>.遍历MemberInfo数组和Object数组,将每个可序列化字段的名称和值写入字节流中。
5.类型中的函数应用OnSerializedAttribute定制特性时,该函数会在序列化对象后调用。
格式化器以反射的方式反序列化字节流:如下所示:
1.类型的字段应用OptionalFieldAttribute定制特性时,当字节流中不存在该字段数据时不会抛出SerializationException。
2.类型中的函数应用OnDeserializingAttribute定制特性时,该函数会在反序列化字节流前调用。
3.执行反序列化字节流的流程如下所示:
1>.从字节流中读取程序集标识和完整类型名称。流程如下所示:
1>>.将程序集加载到AppDomain中,如果加载失败就抛出SerializationException。
2>>.调用FormatterServices的GetTypeFromAssembly函数来获取类型引用对象,如果获取失败就抛出SerializationException。
2>.调用FormatterServices的GetUninitializedObject函数来获取不调用构造函数的类型对象。
3>.调用FormatterServices的GetSerializableMembers函数来获取MemberInfo数组(可反序列化字段信息数组)。
4>.根据字节流中包含的数据创建并初始化一个Object数组(可反序列化字段数值数组)。
5>.调用FormatterServices的PopulateObjectMembers函数来将指定类型对象中的指定字段初始化成指定的数值。
4.类型中的函数应用OnDeserializedAttribute定制特性时,该函数会在反序列化字节流后调用。
格式化器以非反射的方式序列化对象:如下所示:
1.类型必须应用SerializableAttribute定制特性以及实现ISerializable的GetObjectData函数。注意以下几点:
1>.派生类型没有任何额外的字段时,可以不用实现ISerializable的GetObjectData函数;否则就必须实现ISerializable的GetObjectData函数,并在内部调用基类实现ISerializable的GetObjectData函数。
2>.为了避免外界非法调用ISerializable的GetObjectData函数,可以对该函数应用[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)]定制特性,从而让该函数只被格式化器进行序列化对象时调用。
2.执行序列化对象的流程如下所示:
1>.构造SerializationInfo对象。
2>.将SerializationInfo对象作为参数来调用ISerializable的GetObjectData函数。该函数内部会利用SerializationInfo对象的AddValue函数来将对象的字段写入到字节流中。
格式化器以非反射的方式反序列化字节流:如下所示:
1.类型必须提供特殊构造函数以及实现IDeserializationCallback的OnDeserialization函数。注意以下几点:
1>.派生类型没有任何额外的字段时,可以不用提供特殊构造函数;否则就必须提供特殊构造函数,并在内部调用基类提供的特殊构造函数。
2>.为了避免外界非法调用特殊构造函数,可以对该函数应用[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)]定制特性,从而让该函数只被格式化器进行反序列化字节流时调用;
2.执行反序列化字节流的流程如下所示:
1>.从SerializationInfo对象中读取程序集标识和完整类型名称。流程如下所示:
1>>.将程序集加载到AppDomain中,如果加载失败就抛出SerializationException。
2>>.调用FormatterServices的GetTypeFromAssembly函数来获取类型引用对象,如果获取失败就抛出SerializationException。
2>.调用FormatterServices的GetUninitializedObject函数来获取不调用构造函数的类型对象。
3>.调用特殊构造函数来初始化类型对象,并存储SerializationInfo对象。
4>.在IDeserializationCallback的OnDeserialization函数内部利用SerializationInfo对象调用GetX(X为基元类型或者Value)函数或者GetEnumerator函数从字节流中获取字段的值。注意以下几点:
1>>.GetX函数尝试获取字段的类型和字节流中值的类型不符时,格式化器会使用SerializationInfo对象中的格式转换对象调用Convert的ChangeType函数来返回一个IConvertible接口,然后再调用该接口的函数来实现类型转换。
流上下文:类型为StreamingContext,是一个非常简单的值类型。具有以下特性:
1.StreamingContext的公共只读属性如下表所示:
成员名称 | 成员类型 | 说明 |
---|---|---|
State | StreamingContextState | 一组位标志,指定要(反)序列化对象的来源或目的地 |
Context | Object | 额外状态对象引用 |
2.StreamingContextState枚举定义如下表所示:
标志名称 | 标志值 | 说明 |
---|---|---|
CrossProcess | 0x0001 | 来源或目的地是同一台机器的不同进程 |
CrossMachines | 0x0002 | 来源或目的地在不同机器上 |
File | 0x0004 | 来源或目的地是文件。不保证反序列化数据的是同一个进程 |
Persistence | 0x0008 | 来源或目的地是存储。不保证反序列化数据的是同一个进程 |
Remoting | 0x0010 | 来源或目的地是远程的未知位置。这个位置可能在(也可能不在)同一台机器上 |
Other | 0x0020 | 来源或目的地未知 |
Clone | 0x0040 | 对象图被克隆 |
CrossAppDomain | 0x0080 | 来源或目的地是不同的AppDomain |
All | 0x00FF | 来源或目的地可能是上面任何一个标志,这也是默认的设定 |
3.格式化器中提供一个Context属性来供开发人员提供合适的StreamingContext对象。
(反)序列化代理:用来重写类型的(反)序列化行为。以DateTime为例,执行流程如下所示:
1>.定义代理类型。该代理类型实现ISerializationSurrogate的GetObjectData和SetObjectData函数来依次重写序列化和反序列化行为。参考伪代码如下所示:
public sealed class DateTimeSurrogate : ISerializationSurrogate
{
// obj代表序列化对象
// info代表序(反)列化操作对象
// context代表流上下文
public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context)
{
// 使用info对象的AddValue函数来将obj对象写入到字节流中
info.AddValue("Date", ((DateTime)obj).ToUniversalTime().ToString("u"));
}
// obj是反序列化字节流时生成的对象
// info代表序(反)列化操作对象
// context代表流上下文
// selector代表代理选择器对象
// 返回值:返回null等价于返回obj;返回其他对象等价于废弃obj
public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, SurrogateSelector selector)
{
// 使用info对象的GetX(X为基元类型或者Value)函数来获取字节流中的值
DateTime dt = DateTime.ParseExact(info.GetString("Date"), "u", null).ToLocalTime();
#if 使用BinaryFormatter格式化器 && 处理循环引用对象
// 必须修改obj对象中的值且最后要返回该obj对象
FieldInfo fi = typeof(DateTime).GetField("dateData", BindingFlags.NonPublic | BindingFlags.Instance);
fi.SetValue(obj, fi.GetValue(dt));
return null;
#else
return dt;
#endif
}
}
2>.创建格式化器对象。参考伪代码如下所示:
IFormatter formatter = null;
#if 使用BinaryFormatter格式化器
formatter = new BinaryFormatter();
#else
formatter = new SoapFormatter();
#endif
3>.创建代理选择器对象。参考代码如下:
SurrogateSelector ss = new SurrogateSelector();
4>.创建代理对象。其中BinaryFormatter格式化器对象在处理循环引用类型对象时,需要调用
FormatterServices.GetSurrogateForCyclicalReference函数来处理并返回代理对象。参考伪代码如下所示:
ISerializationSurrogate s = new DateTimeSurrogate();
#if 使用BinaryFormatter格式化器 && 处理循环引用对象
s = FormatterServices.GetSurrogateForCyclicalReference(s);
#endif
5>.将类型引用对象,流上下文对象以及代理对象作为参数来调用代理选择器对象的AddSurrogate函数,从而将指定的类型在进行(反)序列化操作时,就调用代理对象中相关的函数进行重写处理。参考代码如下所示:
ss.AddSurrogate(typeof(DateTime), formatter.Context, s);
6>.告诉格式化器对象使用代理选择器对象。参考代码如下所示:
formatter.SurrogateSelector = ss;
7>.调用格式化器对象的Serialize函数时,格式化器对象会从代理选择器对象中查找是否有跟当前要序列化类型相匹配的代理对象。当找到匹配的代理对象时,就调用该代理对象的GetObjectData函数来执行序列化对象操作。
8>.调用格式化器对象的Deserialize函数时,格式化器对象会从代理选择器对象中查找是否有跟当前要反序列化类型相匹配的代理对象。当找到匹配的代理对象时,就调用该代理对象的SetObjectData函数来执行反序列化字节流操作。
(反)序列化绑定:用来将对象反序列化成不同类型。流程如下所示:
1.定义绑定器类型并实现SerializationBinder的BindToName和BindToType函数。参考伪代码如下所示:
public sealed class MySerializationBinder : SerializationBinder
{
// serializedType代表序列化的类型
// 返回值:序列化对象时,更改程序集标识和类型全名
public override void BindToName(Type serializedType, out String assemblyName, out String typeName)
{
}
// assemblyName代表程序集标识
// typeName代表类型全名
// 返回值:反序列化字节流时返回的最终对象类型
public override Type BindToType(String assemblyName, out String typeName)
{
}
}
2.将绑定器对象赋值给格式化器对象的Binder属性。
3.调用格式化器对象的Serialize函数时,格式化器对象如果设置了绑定器属性的话,就会调用绑定器对象的BindToName函数来获取序列化对象时真正想要序列化的程序集和类型。
4.调用格式化器对象的Deserialize函数时,格式化器对象如果设置了绑定器属性的话,就会调用绑定器对象的BindToType函数来获取反序列化字节流时真正要生成的类型。