WCF技术剖析之十四:泛型数据契约和集合数据契约(上篇)

在.NET Framework 2.0中,泛型第一次被引入。我们可以定义泛型接口、泛型类型、泛型委托和泛型方法。序列化依赖于真实具体的类型,而泛型则刻意模糊了具体类型概念。而集合代表一组对象的组合,集合具有可迭代(Enumerable)的特性,可以通过某个迭代规则遍历集合中的每一个元素。由于范型类型和集合类型在序列化和反序列化上具有一些特殊的行为和规则,在这篇文章中,我将会对此进行详细介绍。上篇先来说所泛型数据契约。

一、泛型与数据契约

面向对象通过继承实现了代码的重用,而泛型则实现了“算法的重用”。我们定义一种算法,比如排序、搜索、交换、比较或者转换等等,为了实现尽可能的重用,我们并不限定该算法操作对象的具体类型,而通过一个泛型类型来表示。在真正创建范型对象或者调用该方法的时候,才指定其具体的类型。

就实现来说,泛型是CLR和编程语言(或者是基于编程语言的编译器)共同实现的一种特殊机制;就泛型的概念来说,这是面向对象的范畴。而我们现在介绍的数据契约,则属于面向服务的概念。两者具有一些冲突 ,比如面常服务没有继承、重载的概念一样,面向服务同样也无法理解泛型。

但是基于WCF的编程语言是C#、VB.NET这样的完全面向对象的编程语言,而WCF服务却是基于面向服务的。所以,从某种意义上讲,WCF的一个重大的作用就是弥合面向对象编程(OOP)和面向服务架构(SOA)之间的差异。我们现在就来看看WCF做了些什么使我们能够以泛型类型的形式来定义数据契约。

二、泛型数据契约的默认序列化规则

我们首先通过一个简单的例子看看DataContractSerializer是如何序列化一个范型对象的。为此我定义一个泛型类型Bill<BillHeader, BillDetail>,代表一个一般意义上的单据,BillHeader和BillDetail代表单据报头的明细的类型。两个属性Header和Details表示单据报头和明细列表。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract(Namespace="http://www.artech.com/")]
   4:     public class Bill<BillHeader, BillDetail>
   5:     {
   6:         [DataMember(Order = 1)]
   7:         public BillHeader Header
   8:         { get; set; }
   9:  
  10:         [DataMember(Order = 2)]
  11:         public BillDetail[] Details
  12:         { get; set; }
  13:     }
  14: }

然后我们定义用于描述订单单据的报头和明细的类型:OrderBillHeader和OrderBillDetail。OrderBillHeader描述定单的总体信息,OrderBillDetail实际上表示订单中每一个产品的ID、单价和数量。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract(Namespace="http://www.artech.com/")]
   4:     public class OrderBillHeader
   5:     {
   6:         [DataMember]
   7:         public Guid OrderID
   8:         { get; set; }
   9:  
  10:         [DataMember]
  11:         public DateTime Date
  12:         { get; set; }
  13:  
  14:         [DataMember]
  15:         public string Customer
  16:         { get; set; }
  17:     }
  18:  
  19:     [DataContract(Namespace="http://www.artech.com/")]
  20:     public class OrderBillDetail
  21:     {
  22:         [DataMember]
  23:         public Guid ProductID
  24:         { get; set; }
  25:  
  26:         [DataMember]
  27:         public int Quantity
  28:         { get; set; }
  29:  
  30:         [DataMember]
  31:         public double UnitPrice
  32:         { get; set; }
  33:     }
  34: }

通过下面一个方法创建泛型类型Bill<BillHeader, BillDetail>对象,泛型类型指定为上面定义的OrderBillHeader和OrderBillDetail。

   1: private static Bill<OrderBillHeader, OrderBillDetail> CreateOrderBill()
   2: {
   3:     OrderBillHeader header = new OrderBillHeader
   4:     {
   5:         OrderID     = Guid.NewGuid(),
   6:         Date         = DateTime.Today,
   7:         Customer     = "Foo"
   8:     };
   9:  
  10:     IList<OrderBillDetail> details = new List<OrderBillDetail>();
  11:     OrderBillDetail detail = new OrderBillDetail
  12:     {
  13:         ProductID     = Guid.NewGuid(),
  14:         Quantity     = 20,
  15:         UnitPrice     = 888
  16:     };
  17:     details.Add(detail);
  18:     detail = new OrderBillDetail
  19:     {
  20:         ProductID     = Guid.NewGuid(),
  21:         Quantity     = 10,
  22:         UnitPrice     = 9999
  23:     };
  24:     details.Add(detail);
  25:  
  26:  
  27:     Bill<OrderBillHeader, OrderBillDetail> orderBill = new Bill<OrderBillHeader, OrderBillDetail>()
  28:     {
  29:         Header     = header,
  30:         Details     = details.ToArray<OrderBillDetail>()
  31:     };
  32:     return orderBill;
  33: }

借助在《WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)》定义的Serialize<T>辅助方法,我们对创建Bill<OrderBillHeader, OrderBillDetail>对象进行序列化。最终对象将被序列化成如下的XML。

   1: Bill<OrderBillHeader, OrderBillDetail> orderBill = CreateOrderBill();
   2: Serialize<Bill<OrderBillHeader, OrderBillDetail>>(orderBill, @"orderbill.xml");
   1: <BillOfOrderBillHeaderOrderBillDetail6Of3LqKh xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.artech.com/Artech.DataContractSerializerDemos">
   2:     <Header>
   3:         <Customer>NCS</Customer>
   4:         <Date>2008-12-04T00:00:00+08:00</Date>
   5:         <OrderID>15a62aae-c955-4bc0-acb6-e171fb9fe085</OrderID>
   6:     </Header>
   7:     <Details>
   8:         <OrderBillDetail>
   9:             <ProductID>f7679949-938a-40a0-a32a-5dde5c85e55f</ProductID>
  10:             <Quantity>20</Quantity>
  11:             <UnitPrice>888</UnitPrice>
  12:         </OrderBillDetail>
  13:         <OrderBillDetail>
  14:             <ProductID>bbd750ff-8b0c-48f5-ab1f-5ad7e51bd420</ProductID>
  15:             <Quantity>10</Quantity>
  16:             <UnitPrice>9999</UnitPrice>
  17:         </OrderBillDetail>
  18:     </Details>
  19: </BillOfOrderBillHeaderOrderBillDetail6Of3LqKh>

XML整体的结构正是我们希望的,关键是根节点名称,也就是数据契约的名称,“BillOfOrderBillHeaderOrderBillDetail6Of3LqKh”,会让有些人难以理解。我们仔细分析一下数据契约的名称,会发现它的组成结构是这样的:{类型名称(Bill)}+ Of + {第一个范型参数的类型(OrderBillHeader)} + {第二个范型参数的类型(OrderBillDetail)}+ {哈希值(6Of3LqKh)}。

这里说泛型参数的类型,实际上是不对的,应该说OrderBillHeader和OrderBillDetail的泛型类型对应的数据契约的名称。在下面的代码中。通过 DataContractAttribute特性修改了数据契约的名称(OrderHeader和OrderDetail),最终的数据契约的名称将会变成:BillOfOrderHeaderOrderDetail6Of3LqKh。可以看出描述泛型数据契约的部分内容相应地改变了。可能仔细的读者已经发现了,哈希值部分却没有发生变化,依然是“6Of3LqKh”,这是因为这是泛型类型(含命名空间)的哈希值,而不是数据契约名称的哈希值。所以我们可以将默认的基于泛型类型的命名规则表示成:[类型名称][范型数据契约名称1][ 范型数据契约名称2][…][含命名空间的范型类型哈希值]。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract(Name="OrderHeader")]
   4:     public class OrderBillHeader
   5:     {
   6:         //省略成员
   7:     }
   8:  
   9:     [DataContract(Name = "OrderDetail")]
  10:     public class OrderBillDetail
  11:     {
  12:         //省略成员
  13:     }
  14: }

WCF之所以要采用这样的数据契约命名方式,是为了解决命名冲突,保证数据契约名称的唯一性。我们说了,面向服务下的数据契约完全没有泛型的概念,对它来说所有的类型都是“实实在在”的具体类型。对于泛型类型Bill<BillHeader,BillDetail>,不同的BillHeader和BillDetail组合代表不同的数据契约,所以最终的数据契约的名称需要由自身类型和泛型契约名称派生出来。由于在定义数据契约的时候,不同的CLR类型可以指定相同的数据契约名称,所以加上一个基于所有范型类型(含命名空间)的哈希值可以确保数据契约的唯一性。

WCF在进行元数据发布的时候,会自动按照这样的命名机制创建数据契约,并以XSD的形式发布出来。所以当客户端导入元数据生成客户端代码的时候,生成的等效数据契约的类型名称就是这个经过拼接的名称。下面是Bill<OrderBillHeader, OrderBillDetail>导入的形式。

   1: public partial class BillOfOrderBillHeaderOrderBillDetail6Of3LqKh : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged
   2: {
   3:     //省略成员    
   4: }

三、如何显式指定契约名称

如果你能够确保命名不会发生冲突,你可以通过DataContractAttribute特性的Name属性对数据契约的名称进行显式设置。比如在下面的代码中,将契约名称限定为“OrderBill”。不过这样设置就意味着你假定泛型类型只能表示基于订单的单据了,这相当于失去了泛型的意义。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract(Name="OrderBill")]
   4:     public class Bill<BillHeader, BillDetail>
   5:     {
   6:         //省略成员
   7:     }
   8: }

所以我们可以采用一种动态的设置方式,为数据契约的名称指定一个模板,使用表示泛型数据契约名称和泛型类型哈希值的占位符。其中{0}、{1}表示的是范型数据契约的名称,数字表示相应的范型参数出现的次序,而哈希值则通过{#}表示。所以下面两种范型数据契约是完全等效的。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract]
   4:     public class Bill<BillHeader, BillDetail>
   5:     {
   6:         //省略成员
   7:     }
   8: }
   9: namespace Artech.DataContractSerializerDemos
  10: {
  11:     DataContract(Name="BillOf{0}{1}{#}")]
  12:     public class Bill<BillHeader, BillDetail>
  13:     {
  14:         //省略成员
  15:     }
  16: }

注:部分内容节选自《WCF技术剖析(卷1)》第五章:序列化与数据契约(Serialization and Data Contract)

WCF技术剖析系列:

WCF技术剖析之一:通过一个ASP.NET程序模拟WCF基础架构

WCF技术剖析之二:再谈IIS与ASP.NET管道

WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿

WCF技术剖析之四:基于IIS的WCF服务寄宿(Hosting)实现揭秘

WCF技术剖析之五:利用ASP.NET兼容模式创建支持会话(Session)的WCF服务

WCF技术剖析之六:为什么在基于ASP.NET应用寄宿(Hosting)下配置的BaseAddress无效

WCF技术剖析之七:如何实现WCF与EnterLib PIAB、Unity之间的集成

WCF技术剖析之八:ClientBase<T>中对ChannelFactory<T>的缓存机制

WCF技术剖析之九:服务代理不能得到及时关闭会有什么后果?

WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理

WCF技术剖析之十一:异步操作在WCF中的应用(上篇)

WCF技术剖析之十一:异步操作在WCF中的应用(下篇)

WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)

WCF技术剖析之十三:序列化过程中的已知类型(Known Type)

WCF技术剖析之十四:泛型数据契约和集合数据契约(上篇)

WCF技术剖析之十四:泛型数据契约和集合数据契约(下篇)

WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用

WCF技术剖析之十六:数据契约的等效性和版本控制

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏我是业余自学C/C++的

redis_3.0.7_sds.c_sdsull2str()

1674
来自专栏C/C++基础

CVTE2016春季实习校招技术一面回忆(C++后台开发岗)

2016.3.15,参加了CVTE的技术面,很不幸,我和我的两位小伙伴均跪在了一面。先将当日的面试内容汇总如下,供后来者参考。我们三人各自也都总结了失败的原因,...

531
来自专栏Python爬虫与算法进阶

学点算法之队列的学习及应用

约瑟夫问题 约瑟夫问题 有 n 个囚犯站成一个圆圈,准备处决。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过 k-1个...

3707
来自专栏企鹅FM

深入浅出Kotlin协程

协程(Coroutines)已经随着Kotlin1.3版本一起发布了1.0正式版,android平台可以使用如下方式引入:

3.5K8
来自专栏.NET后端开发

ADO.NET入门教程(六) 谈谈Command对象与数据检索

摘要 到目前为止,我相信大家对于ADO.NET如何与外部数据源建立连接以及如何提高连接性能等相关知识已经牢固于心了。连接对象作为ADO.NET的主力先锋,为用户...

4007
来自专栏JAVA后端开发

activiti多实例节点的任意跳转

activiti是原来不支持节点跳转的,他要求有线才能走,但实际业务中,需要支持动态跳转到各个节点。 一开始,这里的做法是动态构造一条虚拟线的,相关代码如下:

4545
来自专栏Android群英传

Android multidex 主dex是怎么来的?

3692
来自专栏恰同学骚年

设计模式的征途—5.原型(Prototype)模式

相信大多数的人都看过《西游记》,对孙悟空拔毛变出小猴子的故事情节应该都很熟悉。孙悟空可以用猴毛根据自己的形象复制出很多跟自己一模一样的小猴兵出来,其实在设计模式...

943
来自专栏HansBug's Lab

1590: [Usaco2008 Dec]Secret Message 秘密信息

1590: [Usaco2008 Dec]Secret Message 秘密信息 Time Limit: 5 Sec  Memory Limit: 32 MB ...

3586
来自专栏Golang语言社区

Golang视角下的设计模式

这篇文章想聊聊Golang语言下的设计模式问题,我觉得这个话题还是比较有意思的。Golang没有像java那样对设计模式疯狂的迷恋,而是摆出了一份“看庭前花开花...

1172

扫码关注云+社区

领取腾讯云代金券