五,格式化器如何序列化类型实例

为了简化格式化的操作,FCL在System.Runtime.Serialization命名空间提供了一个FormatterServices类型。该类型只包含静态方法,而且不能被实例化。以下的步骤描述了格式化器如何自动序列化一个应用了SerializableAttribute的对象。

1,格式化器调用FormatterServices的GetSerializableMembers方法:

public static MemberInfo[] GetSerializableMembers(Type type,StreamingContext context)

这个方法利用反射获取类型的public和private实例字段(标记了NonSerializedAttribute的字段除外)。方法返回一个MemberInfo对象的数组,其中每个元素都对应于一个可序列化的实例字段。

2,对象序列化,System.Reflection.MemberInfo对象数组传给FormatterServices的静态方法GetObjectData

public static Object[] GetObjectData(Object obj,MemberInfo[] members)

这个方法返回一个object数组,其中每个元素都标识了被序列化的那个对象中的一个字段的值。这个object数组和MemberInfo数组是并行的;换言之,object数组的元素0是MemberInfo数组中的元素0所标识的那个成员的值。

3,格式化器将程序集标识和类型的完整名称写入流中。

4,格式化器然后遍历两个数组中的元素,将每个成员的名称写入流中。

以下的步骤描述了格式化器如何自动反序列化一个(其类型)应用了SerializableAttribute的对象。

1,格式化器从流中读取程序集标识和完整类型名称。

如果程序集没有加载到单前的AppDomain中,加载它。如果程序集不能加载,就抛出SerializationException异常,对象不能序列化。如果程序集已加载,格式化器将程序集信息和类型全名传给FormatterServices的静态方法GetTypeFromAssembly:

public static Type GetTypeFromAssembly(Assembly assem,    string name)

这个方法返回一个System.Type对象,它代表要反序列化的那个对象的类型。

2,格式化器调用FormatterServices的静态方法GetUninitializedObject:

public static Object GetUninitializedObject(Type type)

这个方法为一个新对象分配内存,并不为对象调用构造器。然后,对象的都有字节都被初始化为null或0。

3,格式化器现在构造并初始化一个MemberInfo数组。

具体做法和前面的做法一样,都是调用FormatterServices的GetSerializableMembers方法。这个方法返回序列化好,现在需要反序列化的一组字段。

4,格式化器根据流中包含的数据创建并初始化一个object的数组。

5,将新分配的对象、MemberInfo数组以及并行object数组(其中包含字段值)的引用传给FormatterServices的静态方法PopulateObjectMembers:

public static Object PopulateObjectMembers(Object obj,MemberInfo[] members, Object[] data)

这个方法遍历数组,将每个字段初始化成对应的值。到此为止,对象就算是被彻底反序列化了。

六,控制序列化/反序列化的数据

前面讨论过,控制序列化和反序列化的最佳方式就是使用OnDeserializedAttribute, OnDeserializingAttribute,OnSerializedAttribute,OnSerializingdAttribute,NonSerializedAttribute和OptionalFieldAttribute等特性。然而,在一些极少见的情况下,这些attribute不能提供你希望的全部控制。除此之外,格式化器在内部使用反射,而反射的速度比较慢,这会增大序列化和反序列化的时间。为了对序列化和反序列化数据进行完全的控制,并避免使用反射,你的类型可以实现System.Runtime.Serialization.ISerializable接口,它的定义如下:

    public interface ISerializable{void GetObjectData(SerializationInfo info, StreamingContext context);}

这个接口只有一个方法,即GetObjectData。但是实现这个接口的大多数类型还实现了一个特殊的构造器。

重要提示:ISerializable接口的一个大问题在于,一旦类型实现了它,所有的派生类型也必须实现它,而且派生类型必须保证调用基类的GetObjectData方法和特殊构造器。除此之外,一旦类型实现了该接口,便永远不能删除它,否则会失去与派生类型的兼容性。

重要提示:ISerializable接口和特殊的构造器旨在有格式化器使用。然而其他一些代码可能调用GetObjectData,后者可能返回敏感的数据。另外,其他代码可能构造一个对象,并传入损坏的数据。因此,建议将以下attribute应用于GetObjectData方法和特殊构造器:

[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]

格式化器序列化一个对象时,会检查每个对象。如果发现一个对象的类型实现了ISerializable接口,格式化器就会忽略所有定制attribute,改为构造一个System.Runtime.Serialization.SerializationInfo对象。这个对象包含了要实际为对象序列化的值的集合。

构造一个SerializationInfo时,格式化器要传递两个参数:Type和System.Runtime.Serialization.IFormatterConverter。Type标识要序列化的对象。为了唯一的标识一个类型,需要两个部分的信息:类型的字符串名称及其程序集的标识。一个SerializationInfo对象构造好后,会包含类型的全名,并将这个字符串存储在一个私有字段中。可以通过SerializationInfo的FullTypeName属性查询。类似的,构造器获取类型的定义程序集(通过在内部查询Type的Module属性,再查询Module的Assembly属性,在查询Assembly的FullName属性),并将这个字符串存储在一个私有字段中。可以通过SerializationInfo的AssemblyName属性查询。虽然可以设置这两个属性,但不建议这么做。如果想更改序列化的类型,可以调用SerializationInfo的SetType方法,调用这个方法后,这两个属性会被正确的设置。构造好并初始化好SerializationInfo对象后,格式化器调用类型的GetObjectData方法,向它传递对SerializationInfo对象的引用。GetObjectData方法负责决定需要哪些信息来序列化对象,并将这些信息添加到SerializationInfo对象中。GetObjectData调用SerializationInfo提供的AddValue方法的众多重载版本之一来指定要序列化的信息,针对每个要添加的数据,都要调用一次AddValue方法。

以下的代码展示了自定义的MyType类型如何实现ISerializable和IDeserializationCallback接口来控制其对象的序列化和反序列化工作。

        [Serializable]public class MyType : ISerializable, IDeserializationCallback{Int32 x, y;Int32 sum;SerializationInfo m_siInfo;public MyType(int x, int y){this.x = x; this.y = y; this.sum = x + y;}//用于控制反序列化的特殊构造器,参数列表必须和GetObjectData一致//这个构造器会在OnDeserialization之前调用protected MyType(SerializationInfo info, StreamingContext context){m_siInfo = info;}#region ISerializable//用于控制序列化的方法public void GetObjectData(SerializationInfo info, StreamingContext context){//这里只序列化了x和y,并没有suminfo.AddValue("left", x); //注意这里的名称可以任意取,不一定是字段名info.AddValue("right", y);}#endregion#region IDeserializationCallback//所有key/value对象都反序列化好后调用的方法public void OnDeserialization(object sender){if (m_siInfo == null) return;this.x = m_siInfo.GetInt32("left");//读取的时候,名称要和GetObjectData指定的一致this.y = m_siInfo.GetInt32("right");this.sum = x + y;}#endregion}

●每个AddValue方法都获取一个string名称和一些数据。数据一般是简单类型,如Int32,Boolean,DateTime等。然而也可以传递一个object的引用。GetObjectData添加好所有必要的序列化信息之后,会返回值格式化器。注意,如果一个字段类型实现了ISerializable接口,不要调用GetObjectData方法,相反直接调用AddValue添加字段。格式化器会帮你调用GetObjectData。现在,格式化器获取已经添加到SerializationInfo对象的所有值,并把它们序列化到流中。

●知道了如何设置序列化所需的全部信息之后,再来看反序列化。格式化器从流中提取一个对象时,会为对象分配内存(调用FormatterServices的静态方法GetUninitializedObject)。最初,这个对象的所有字段都设置成null或0。然后格式化器查看类型是否实现了一个ISerializable接口。如果存在这个接口,格式化器尝试调用一个特殊构造器,它的参数和GetObjectData方法完全一致。如果你的类型是sealed,建议将这个构造器声明为private,防止任何代码不慎调用它。注意,无论这个构造器是如何声明的,格式化器都能调用它。

●构造器获取一个对SerializationInfo对象的引用。在这个SerializationInfo对象中,包含了对象序列化时添加的所有值。特殊构造器可以调用GetInt32,GetBoolean等方法获取设定的值。反序列化时,调用的GetXXX方法一定要和GetObjectValue方法调用的AddValue时传递的字段类型一致。它们是一一匹配的,如果类型不一致,格式化器会尝试用一个IFormatterConverter对象将流中的值“转型”成你指定的值。

●构造SerializationInfo时,需要传递一个实现IFormatterConverter接口的对象。由于SerializationInfo的创建是由格式化器负责的,所有由它来选择想要的IFormatterConverter类型。Microsoft的BinaryFormatter和SoapFormatter类型都是构造System.Runtime.Serialization.FormatterConverter的一个实例。Microsoft的格式化器没有提供任何方式让我们自己选择IFormatterConverter类型。

●FormatterConverter类型调用System.Convert类的各种静态方法在不同的类型之间进行转化。然而,为了在其他任意类型之间进行转换,FormatterConverter类型要调用Convert的ChangeType方法将序列化好的类型转化成一个IConvertible接口,在调用恰当的方法。所有,要允许一个可序列化类型的对象反序列化成一个不同的类型,可以考虑自己实现IConvertible接口。注意,在反序列化时调用一个Get方法,只有发现它的类型和流中的类型不一致时,才会使用FormatterConverter对象。

●特殊构造器也可以不调用Get方法,而是调用GetEnumerator,返回一个System.Runtime.Serialization.SerializationInfoEnumerator。该对象可以遍历SerializationInfo的所有值。枚举的每个值都是System.Runtime.Serialization.SerializationEntry对象。

● 当然,你完全可以定义一个自己的类型,让它从实现了ISerializable的GetObjectData方法和特殊构造器的一个类型派生。如果你的类型也实现了ISerializable,那么在你实现的GetObjectData方法和特殊构造器中,必须调用基类的同名方法,确保对象能序列化和反序列化,这一点必须牢记 。如果派生类中没有额外的字段,因而没有特殊的序列化/反序列化的需求,就完全不必实现ISerializable。和所有的接口成员相似,GetObjectData是virtual的,调用它可以正确的序列化对象。除此之外,格式化器将特殊构造器视为“已虚拟化”(virtualized)。换言之,反序列化期间,格式化器会检查要序列化的类型。如果那个类型没有提供特殊构造器,格式化器会扫描基类,直到找到实现特殊构造器的类。

重要提示:特殊构造器中的代码一般会从传给它的SerializationInfo对象中提取字段。提取字段后,不能保证对象已完全序列化,所以,特殊构造器不应尝试操纵它的对象。如果你的类型必须访问提取的一个对象中的成员(比如调用一个方法),建议你的类型提供一个应用了OnDeserialized特性的方法,或者让你的类型实现IDeserializationCallback接口的OnDeserialization方法。调用该方法时,所有的字段都已设置好。然而对于多个对象来说,它们的OnDeserialized或OnDeserialization的调用顺序是没有保障的。所以,虽然字段可能已经初始化,但你仍然不知道被引用的对象是否已完全反序列化好(如果那个被引用的对象也实现了OnDeserialized或IDeserializationCallback)。

如何在基类没有实现ISerializable接口的前提下定义一个实现它的类型

前面已经讲过,ISerializable的功能非常强大,它允许一个类型完全控制如何对类型的实例进行序列化和反序列化。然而,这个能力是有代价的,子类必须负责基类所有字段的序列化。如果基类实现了ISerializable接口,子类只需调用基类的GetObjectData方法即可。

但有一天,我们需要对象子类进行序列化,但发现它的基类没有实现ISerializable接口。在这种情况下,派生类必须手动序列化基类的字段,具体做法就是获取他们的值,并把这些值添加到SerializationInfo集合中。然后在特殊构造器中,还必须从集合中取出这些值,并以某种方式设置基类的字段。如果基类的字段是public或protected字段,那么这一切都容易实现。但如果基类的字段是private字段,就很难或更本不可能实现。下面的例子演示了如何正确实现ISerializable的GetObjectData和他的特殊构造器使基类的字段能被序列化:

        [Serializable]internal class Base{protected string m_name;public Base() { }public Base(string name) { m_name = name; }}[Serializable]internal class Derived : Base, ISerializable{private DateTime m_date;public Derived(string name): base(name){m_date = DateTime.Now;}//如果这个特殊构造器不存在,会抛出SerializationException异常[SecurityPermission(SecurityAction.Demand, SerializationFormatter = false)]private Derived(SerializationInfo info, StreamingContext context){//为本类设置反序列化好的值m_date = info.GetDateTime("date");//查找基类的可序列化字段集合Type baseType = this.GetType().BaseType;MemberInfo[] members = FormatterServices.GetSerializableMembers(baseType, context);foreach (MemberInfo mi in members){//为基类设置反序列化好的值FieldInfo fi = (FieldInfo)mi;fi.SetValue(this, info.GetValue(baseType.FullName + "+" +mi.Name, fi.FieldType));}}#region ISerializable public void GetObjectData(SerializationInfo info, StreamingContext context){//为本类序列化值info.AddValue("date", m_date);//查找基类的可序列化字段集合Type baseType = this.GetType().BaseType;MemberInfo[] members = FormatterServices.GetSerializableMembers(typeof(Derived).BaseType, context);foreach (MemberInfo mi in members){//为基类设置序列化值,最好加上基类的前缀名,避免和子类有同名成员时冲突info.AddValue(baseType.FullName + "+" +mi.Name, ((FieldInfo)mi).GetValue(this));}}#endregion}

特别注意:经实验发现,调用特殊化构造器的时候,会去调用基类的默认构造函数,感觉这个设计似乎有些问题。

未完待续,下接《CLR via C#》笔记——运行时序列化(3)

转载于:https://www.cnblogs.com/xiashengwang/archive/2012/07/18/2598123.html

《CLR via C#》笔记——运行时序列化(2)相关推荐

  1. Java 虚拟机学习笔记 | 运行时数据区总结

    前言 要想学习好 Java,Java虚拟(JVM)的学习是绕不开的.学习 Java虚拟(JVM)首先就要先了解的就是Java虚拟(JVM)运行时数据区. 在Java语言和虚拟机规范中对运行时数据区进行 ...

  2. java虚拟机笔记—运行时数据区域

    程序计数器 1.程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器. 2.是唯一一个在java虚拟机规范中没有规定任何outOfMemoryError情况区域. 3.线程私 ...

  3. 深入探索.NET内部了解CLR如何创建运行时对象

    前言 SystemDomain, SharedDomain, and DefaultDomain. 对象布局和内存细节. 方法表布局. 方法分派(Method dispatching). 因为公共语言 ...

  4. [转载]深入探索.NET框架内部了解CLR如何创建运行时对象

    深入探索.NET框架内部了解CLR如何创建运行时对象 发布日期: 9/19/2005 | 更新日期: 9/19/2005 Hanu Kommalapati Tom Christian 本文讨论: • ...

  5. 深入探索.NET框架内部了解CLR如何创建运行时对象

    为什么80%的码农都做不了架构师?>>>    本文讨论: • SystemDomain, SharedDomain, and DefaultDomain • 对象布局和内存细节. ...

  6. 科维PLC运行时系统ProConOS embedded CLR 2.2 特定应用

    ProConOS embedded CLR是新型的开放式标准化PLC运行时系统,符合IEC 61131标准,可执行不同的自动化任务(PLC.PAC.运动控制.CNC.机器人和传感器). 通过采用国际标 ...

  7. 《Effective C#》读书笔记-1.C# 语言习惯-2.使用运行时常量(readonly)而不是编译时常量(const)...

    概念 编译时 编译时顾名思义就是正在编译的时候.那啥叫编译呢?就是编译器帮你把源代码翻译成机器能识别的代码.(当然只是一般意义上这么说,实际上可能只是翻译成某个中间状态的语言.比如Java只有JVM识 ...

  8. CLR运行时细节 - 继承多态的实现

    关于多态不多解释了,在运行时决定和调用具体的实现,是面向对象的基础 设计模式的基础. 准备把继承多态和接口多态分开,因为从CLR实现的角度继承多态相比于接口多态要简单得多,也更容易理解,本篇只讨论继承 ...

  9. CLR运行时细节 - Method Descriptor

    方法描述符:MethodDesc 运行时用来描述类型的托管方法,它保存在方法描述桶(MethodDescChunk)内; 方法描述符保存了方法在运行时的一些重要信息: 是否JIT编译; 是否有方法表槽 ...

  10. C++工作笔记-编译时类型检查与运行时类型检查

    转载链接如下: https://blog.csdn.net/u013298353/article/details/17676959 编译时 编译时顾名思义就是正在编译的时候.那啥叫编译呢?就是编译器帮 ...

最新文章

  1. app图标圆角角度_?APP图标造型分析
  2. 归纳苹果,Facebook大规模部署的Spark-用户界面详细执行操作。
  3. 活动推荐丨阿里云TechInsight论坛为什么这么火?
  4. S5PV210开发 -- UART 详解
  5. arcgis python实例_arcgis python脚本工具实例教程—栅格范围提取至多边形要素类
  6. eclipselink mysql_Eclipselink更新现有表
  7. MATLAB实现LDA(线性判别分析),以两个类别数目为例
  8. 桌面虚拟化之应用程序的整合
  9. mybatis开启log_mybatis使用spring-druid数据源连接池配置log4j打印sql语句以及开启监控平台...
  10. hibernate入门二之单表操作
  11. 关于伪分布式hadoop集群及HBase安装的一些记录(基于林子雨老师的大数据软件安装和基础编程)
  12. 处理器管理及并发进程-多道程序设计
  13. 2020年的触动心灵的鸡汤
  14. 双卡4G路由器_4G双卡双模路由器_4G双网双待路由器
  15. wordpress html5视频播放插件,WordPress插件DPlayer支持m3u8视频HTML5播放
  16. Line Phone概念手机的设计感悟
  17. 华三模拟器:IPV6路由实验
  18. The missing semester of your CS education--命令行环境
  19. 2018 最新注册码【激活码】、在线激活 pycharm 完整方法(亲测有效)【2018.05.08 重大更新!!!!】
  20. matplotlib之pyplot模块——填充多边形(fill)

热门文章

  1. [网络流24题] 最长k可重线段集问题 (费用流)
  2. 圣杯布局(三栏布局)
  3. 我这么玩Web Api(二):数据验证,全局数据验证与单元测试
  4. header简单用处
  5. leetcode(90)子集 2
  6. EasyUi通过POI 实现导出xls表格功能
  7. 使用 classList API
  8. Python version 3.3 required, which was not found in the registry
  9. 在 Linux 中怎样将 MySQL 迁移到 MariaDB 上
  10. 对“才鸟”——动态显示扩展数据的改写