当试图通过Web服务、WCF这样的远程处理技术将一个对象复制到远端时,具有对类型序列化的能力很关键。

一 序列化基础

  序列化描述了持久化或传输一个对象的状态到流的过程(.NET将对象序列化到流,流是字节的逻辑序列,与特定的介质无关)。被持久化的数据次序包括所有以后需要用来重建(反序列化)对象所需要的信息。通过序列化,我们用最小的花费来保存或传输海量的数据。使用[Serializable]序列化对象非常简单,但幕后的调用过程十分复杂。当一个对象被序列化时,其集成连上的所有数据都会被包括进来,一组相关的对象使用对象图来表现。.NET允许使用多种格式来保存一个对象图,包括二进制、SOAP和XML,对象图可以持久化为任意的System.IO.Stream派生类。

(一) 对象图

  当对象被序列化时,CLR将处理所有相关的对象,一组关联对象被总称为一个对象图。它提供了一种很简明的方式来记载一组对象的引用关系。对象图中的每个对象被赋予一个独有的数值类型的值,这个值可任意地赋给对象图中的成员,以此来记录对象的依赖关系。

  上图可知,Order引用了Product,OnlineOrder引用了Order和Product。在CLR中,这种关系表示为:[Order 3,ref 2],[Product 2],[OnlineOrder 1,ref 3,ref 2]。当序列化OnlineOrder时,根据对象图,使Order和Product参与其中。(注意XmlSerializer不使用对象图。)

(二) 使用序列化

  .NET通过反射自动实现了对象的序列化和反序列化。.NET能够捕获对象的每个字段的值,并将其序列化。对于反序列化,.NET使用反射创建一个对应类型的新对象,读取它的持久化字段值,然后设置字段值。由于反射可以打破封装,所以.NET在序列化期间能够完整地反映出对象的状态,从而保证了反序列化可以完整地重建对象状态。通过为类型添加[Serializable]将默认类型是所有数据成员参与序列化,使用[NonSerialized]可以指定不需要序列化的成员。当.NET序列化成员时,首先通过反射确定它是否具有[NonSerialized]特性,如果有就会忽略该字段,直接跳过他。[Serializable]不能被继承,使用必须在继承了链的每个类型上进行声明。实际上,对象图中的所有对象必须被标记为[Serializable]。(因为XmlSerializer不使用对象图,所以不需要对类型标记[Serializable]。)

  System.Runtime.Serialization.IFormater定义了核心的Serialize()和Deserialize()方法,Serialize()和Deserialize()将完成对象图和流之间的转换。.NET为类型的序列化和反序列化提供了两种格式器,BinaryFormatter会将其序列化为二进制格式,SoapFormatter则使用.NET特定的SOAP XML格式。它们都实现了IFormater接口。除了持久化对象的状态,这两种格式器都会将类型的程序集以及版本控制信息持久化到流中,以保证反序列化为正确类型。

  System.Runtime.Remoting.Messaging.IRemotingFormatter派生于IFormater,它重载了Serialize()和Deserialize()成员使风格更适合于分布式持久化。

  不同的序列化器对类型保真(type fidelity)的支持程度不同,其反应在对私有字段的序列化以及对程序集信息的序列化上。如果希望序列化的对象可以被.NET之外的其它环境使用,XmlSerializer是理想的选择。(Web Service默认使用XmlSerializer)

1 二进制

  BinaryFormatter类型使用紧凑的二进制格式将对象图序列化为一个流,这个类型在System.Runtime.Serialization.Formatters.Binary命名空间中定义。

  使用BinaryFormatter时,不仅仅将对象图中对象的字段数据进行序列化(所有未标记[NonSerialized]的字段,包括公有的和私有的),还将序列化每个对象类型的完全限定名和定义程序集的完整名称(名称、版本、公钥、区域性)。这些数据使BinaryFormatter在希望用值(Marshaling By Value,按值编组)跨越.NET应用程序机器边界传递对象时成为理想的选择。

  可以使用下列方式干预BinaryFormatter的序列化过程:

  • 为字段标记[NonSerialized]。
  • 实现ISerializable接口自定义序列化过程。
  • 实现IDeserializationCallback,为对在完成对象图序列化时所调用的方法的部分支持。(系统在反序列化的时候会检查待类型是否实现了IDeserializationCallback接口,如果实现了,那么系统就调用该接口中的OnDeserialization方法。)
  • 设置Binder属性。

2 SOAP

  SoapFormatter类型将对象序列化为一个SOAP消息,这个类型在System.Runtime.Serialization.Formatters.Soap命名空间中定义。

  SoapFormatter通过使用XML命名空间来序列化原始程序集的信息,所有未标记[NonSerialized]的字段,包括公有的和私有的都会被序列化,无法序列化泛型,可以序列化数组。

  可以使用下列方式干预SoapFormatter的序列化过程:

  • 使用相关特性,如SoapIgnoreAttribute。
  • 实现IDeserialilzationCallback。
  • 设置Binder属性。

3 XML

  XmlSerializer类型将对象序列化为XML,这个类型在System.Xml.Serialization命名空间中定义。

  XmlSerializer不具备类型保真(原因是XML数据表现形式具有终端开放性),因此也不记录类型完全限定名称或起源的程序集,只有公有字段或拥有公有属性的私有字段才会被序列化,为通过属性公开的私有字段会被忽略。

  XmlSerializer在默认的情况下具有如下的序列化和反序列化规则:

  • XML根节点的名称为对象类型的名称,并且没有命名空间。
  • 对象属性或字段成员以XmlElement的形式输出,名称和属性/字段名称一致,并且不具有命名空间。
  • 只有public属性/字段成员才会参与序列化。
  • 只有可读/写的属性才能被序列化。
  • XmlElement的顺序与对应的属性或字段在类型中定义的顺序一致。
  • 无参构造器是必须的,因为反序列化的时候需要调用它创建对象。

  可以使用下列方式干预XmlSerializer的序列化过程:

  • 使用相关特性,如XmlIgnoreAttribute。
  • 实现IXmlSerializable。
  • 实现IDeserialilzationCallback。
  • 用XmlTypeMapping或XmlAttributeOverrides构造XmlSerializer。

二 WCF序列化

  原有的格式化器需要将类型的程序集及版本控制信息持久化到流中,以保证对象被反序列化为正确的类型,这种方式一定程度上妨碍面向服务交互。原因如下:BinaryFormatter和SoapFormatter具备类型保真,它们要求其所在程序集、版本等信息必须完全一致,如果不同则视为是不同类型的对象。面向服务的交互方式应该更倾向于平台无关,并最大限度的解耦,因此这2个格式化器显然与WCF有些不搭。再看XmlSerializer格式化器,在功能上与DataContractSerializer格式器类似,但是为何没有使用XmlSerializer格式器而是引人新格式器,我的理解如下:在SOA服务的构建过程中,提倡契约优先的构建原则,也就是说我们应该优先构建契约后构建具体的代码,在构建过程中并不考虑其具体的序列化过程。(契约优先相对于代码优先在设计层面具有更高的抽象程度,降低了服务设计与平台的耦合度,因此我更倾向于契约优先的开发顺序。)此外,XmlSerializer为如何将数据表示成XML提供了精确的控制,DataContractSerializer则提供了较少的控制,所以DataContractSerializer的序列化过程是可预知的,容易优化,所以它与XmlSerializer相比有更好的性能(我查到的数字是提升了约10%)。

  DataContractSerializer通过WriteObject和ReadObject方法完成序列化和反序列化。DataContractSerializer具有如下5个重要属性的成员,以及多个构造器重载来指定它们:

  • DataContractSurrogate 返回一个数据契约代理类的对象,数据契约代理实现了IDataContractSurrogate,可以用于干预DataContractSerializer的序列化、反序列化,以及契约的导入和导出行为。
  • IgnoreExtensionDataObject 是否忽略扩展数据对象。
  • KnownTypes 已知类型集合,包含了在序列化和反序列化时不能被解析的类型。
  • MaxItemsInObjectGraph 进行序列化和反序列化允许的最大对象数,默认为65536。
  • PreserveObjectReferences 表示如果数据对象的多个属性或字段引用相同的对象,在序列化的时候是否要在XML中保持一样的引用结构。

  WCF格式器DataContractSerializer并没有实现IFormater,而是继承自XmlObjectSerializer,在使用其实现类型与流之间的序列化操作时,代码与BinaryFormatter和SoapFormatter的写法略微不同,需要调用带参数的构造函数来提供操作的类型,其代码形如:

            DataContractSerializer dcs = new DataContractSerializer(typeof(Product));
using (Stream fs = new FileStream("test.soap", FileMode.Create, FileAccess.Write, FileShare.None))
{
dcs.WriteObject(fs, product);//反序列化使用ReadObject方法,但返回的是Object类型,需要进行显示类型转换。
}

  序列化结果如下:

  DataContractSerializer在默认的情况下具有如下的序列化规则:

  • XML的根节点名称为数据契约类型的名称,默认的命名空间为http://schemas.datacontract.org/2004/07/{数据契约类型的命名空间}。
  • 只有显式地应用了[DataMember]的字段或属性才能作为数据成员参与序列化。
  • 所有数据成员均以XML元素的形式被序列化。
  • 序列化后数据成员在XML中的次序为:父数据成员在前,子类数据成员在后,定义在同一个类型中的数据成员按照字母排序。

  限定序列化对象的数量

  MaxItemsInObjectGraph属性表示允许被序列化或反序列化对象的数量上限。一旦超过该值,序列化或反序列化会立即终止并抛出异常,从而在一定程度上缓解了通过发送大集合数据形式的拒绝服务攻击的风险。其计算规则为:对象自身算一个对象,使用成员及所有内嵌的成员都算一个对象。

  保持对象现有的引用结构

  PreserveObjectReferences属性表示是否在序列化的时候保持相应的引用结构。

  此外,WCF还定义了NetDataContractSerializer格式器,它实现了IFormater接口(为了兼容旧的序列化模式),所以它和旧的格式器相似,可以序列化类型信息,其使用方法也与传统序列化器类似,其代码形如:

            IFormatter formatter = new NetDataContractSerializer();
using (Stream fs = new FileStream("test.soap", FileMode.Create, FileAccess.Write, FileShare.None))
{
formatter.Serialize(fs, product);
}

  序列化结果如下:

  当类型同时使用[DataContract]和[Serializable]修饰时,NetDataContractSerializer以[DataContract]的设置为准。使用NetDataContractSerializer序列化的对象,可以使用DataContractSerializer反序列化,但反之不行。

三 数据契约与旧序列化模式

  • [Serializable]和[DataContract]可以同时使用。
  • [Serializable]指明类型的所有成员均参与序列化,要求开发者显示指定不参与序列化的成员;而[DataContract]以明确参与的方式,要求开发者显示指定参与序列化的成员。
  • [Serializable]不支持别名,也无法将一个新类型映射为预定义的数据契约;而[DataContract]支持别名,并且可以指定成员顺序。
  • [Serializable]会隐式序列化未标记[NonSerialized]的私有字段,破坏属性的封装性;而[DataContract]只会序列化指定的成员,即使成员是私有的。
  • [Serializable]没有直接支持版本控制,因为它获取了程序集信息,不容易进行控制;而[DataContract]易于版本控制。

四 新旧序列化的选择

  选择旧序列化的目的在于创建更为复杂的对象。如果使用WCF默认的格式器,我们必须为成员同时提供getter、setter方法,且其为了满足SOA的需要,传递的内容无异于XML数据。这类对象通常被称为DTO(Data Transfer Object,数据传输对象)。这是一种简单的对象,不包含业务逻辑,不与数据以外的类型关联,可以理解为简单的数据容器。该容器是非智能的、不受保护的、不安全的,我们无法限制客户端局部初始化一个对象或者往容器中注入无效数据。

  所以当我们需要为跨平台而使用DTO时,我们应该选择数据契约;而当我们想要创建复杂的传输对象时,[Serializable]显然更合适,它不仅会序列化对象的数据也会序列化任何处理事件的对象(使用时要注意,可能会发生你预料之外的序列化错误),但是你将失去跨平台的特性。

WCF学习笔记——对象序列化的更多相关文章

  1. Jackson学习笔记-对象序列化

    一.用ObjectMapper.readValue(jsonString, Student.class) , ObjectMapper.writeValueAsString(student) impo ...

  2. WCF学习笔记之序列化

    DataContractAttribute 与 DataMenberAttribute DataContractAttribute该特性只能用于枚举.类和结构体,而不能用于接口:又因为DataCont ...

  3. WCF 学习笔记之异常处理

    WCF 学习笔记之异常处理 1:WCF异常在配置文件 <configuration> <system.serviceModel> <behaviors> <s ...

  4. WCF学习笔记之事务编程

    WCF学习笔记之事务编程 一:WCF事务设置 事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元: WCF通过System.ServiceModel.TransactionFlowA ...

  5. WCF学习笔记之传输安全

    WCF学习笔记之传输安全 最近学习[WCF全面解析]下册的知识,针对传输安全的内容做一个简单的记录,这边只是简单的记录一些要点:本文的内容均来自[WCF全面解析]下册: WCF的传输安全主要涉及认证. ...

  6. WCF 学习笔记之双工实现

    WCF 学习笔记之双工实现 其中 Client 和Service为控制台程序 Service.Interface为类库 首先了解契约Interface两个接口 using System.Service ...

  7. EF + WCF学习笔记——EF实体类序列化

    项目中如果 EF + WCF 结合使用,模式应该是EF负责读取数据库,传递数据对象给WCF,WCF再将这些对象传送给客户端.因为WCF传送的对象需要序列化,而EF默认生成的对象并没有序列化,很可能会出 ...

  8. WCF学习笔记(基于REST规则方式)

    一.WCF的定义 WCF是.NET 3.0后开始引入的新技术,意为基于windows平台的通讯服务. 首先在学习WCF之前,我们也知道他其实是加强版的一个面向服务(SOA)的框架技术. 如果熟悉Web ...

  9. .net学习笔记---xml序列化

    XML序列化是将对象的公共属性和字段转换为XML格式,以便存储或传输的过程.反序列化则是从XML输出中重新创建原始状态的对象.XML序列化中最主要的类是XmlSerializer类.它的最重要的方法是 ...

随机推荐

  1. crontab FAQ

    1.crontab变量问题 crontab中的脚本须要引入系统变量才干找到,否则crontab中的命令找不到系统变量,或者都写绝对路径. 2.1分钟运行一次 */1 * * * * /etc/keep ...

  2. maven项目运行没问题,但是项目名上有一把×

    原因有以下:你先要自己进行检查,看是出现的什么错误 有红叉,并不代表编译和运行就出错.到Windows—Show view—Problems下看看到底报了什么错.像一些validation出错,项目虽 ...

  3. adb-端口被占用解决办法(win)

    今天在window下使用adb的时候,提示我说5037端口被占用. * daemon not running. starting it now on port * ADB server didn't ...

  4. 经典的横线中间文字css布局---flex布局

    html: <div class="title"> <div class="line"></div> <div cla ...

  5. 洛谷——P2957 [USACO09OCT]谷仓里的回声Barn Echoes

    https://www.luogu.org/problem/show?pid=2957 题目描述 The cows enjoy mooing at the barn because their moo ...

  6. 洛谷 P1850 换教室

    P1850 换教室 题目描述 对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程. 在可以选择的课程中,有 2n2n 节课程安排在 nn 个时间段上.在第 ii(1 \leq ...

  7. hdu 3294 Girls&#39; research

    #include<stdio.h> #include<string.h> #define MAX 200020 char s[MAX],ss[MAX*2],str[2]; in ...

  8. HDU 3072--Intelligence System【SCC缩点新构图 &amp;&amp; 求连通全部SCC的最小费用】

    Intelligence System Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Othe ...

  9. win7-时间更新

    今天发现电脑的时间不对,后来就自己摸索了时间的自动更新方法.自己记录下来,以方便以后忘了查询 点击电脑右下角的时间->选择更改日期和时间设置->选择internet->更改设置-&g ...

  10. 二维LIS(CDQ分治)

    题目描述 给定一个长度为N的序列S,S的每个元素pi是一个二元组(xi,yi),定义pi<pj当且仅当xi<xj并且yi<yj,求S的最长上升子序列长度 输入格式 第一行一个N,表示 ...