閱讀275 返回首頁    go 技術社區[雲棲]


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="https://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="https://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="https://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="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://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技術剖析之十六:數據契約的等效性和版本控製



作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-30 13:33:34

  上一篇:go  百度自動駕駛事業部總監孫勇義:Apollo計劃背後的AI技術| 清華x-lab AI研習社
  下一篇:go  Spring MVC的配置