閱讀259 返回首頁    go 微軟 go Office


WCF技術剖析之十四:泛型數據契約和集合數據契約(下篇)

在.NET中,所有的集合都實現了IEnumerable接口,比如Array、Hashtable、ArrayList、Stack、Queue等。有的集合要求元素具有相同的類型,這種集合一般通過泛型的方式定義,它們實現另一個接口IEnumerable<T>(IEnumerable<T>本身繼承自IEnumerable),這樣的集合有List<T>、Dictionary<TKey,TValue>、Stack<T>、Queue<T>等。基於集合類型的序列化具有一些特殊的規則和行為,在上篇中我們詳細介紹了基於泛型數據契約的序列化規則,接下來我們介紹基於集合對象的序列化,以及基於集合類型的服務操作。

一個集合對象能夠被序列化的前提是集合中的每個元素都能被序列化,也就是要求元素的類型是一個數據契約(或者是應用了SerialiableAttribute特性)。雖然集合具有各種各樣的表現形式,由於其本質就是一組對象的組合,DataContractSerializer在對它們進行序列化的時候,采用的序列化規則和序列化過程中表現出來的行為是相似的。比如我們現在需要通過DataContractSerializer序列化一個Customer對象的集合,Customer類型定義如下。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [DataContract(Namespace="https://www.artech.com/")]
   4:     public class Customer
   5:     {
   6:         [DataMember(Order = 1)]
   7:         public Guid ID
   8:         { get; set; }
   9:  
  10:         [DataMember(Order=2)]
  11:         public string Name
  12:         { get; set; }
  13:  
  14:         [DataMember(Order = 3)]
  15:         public string Phone
  16:         { get; set; }
  17:  
  18:         [DataMember(Order = 4)]
  19:         public string CompanyAddress
  20:         { get; set; }
  21:     }
  22: }

現在我通過我們前麵定義的範型Serialize<T>對以下3種不同類型的集合對象進行序列化:IEnumerable<Customer>、IList<Cusomter>和Customer[]。

   1: Customer customerFoo = new Customer
   2:      {
   3:          ID             = Guid.NewGuid(),
   4:          Name         = "Foo",
   5:          Phone         = "8888-88888888",
   6:          CompanyAddress     = "#9981, West Sichuan Rd, Xian Shanxi Province"
   7:      }; 
   8:  
   9: Customer customerBar = new Customer
  10: {
  11:     ID         = Guid.NewGuid(),
  12:     Name         = "Bar",
  13:     Phone         = "9999-99999999",
  14:     CompanyAddress     = "#3721, Taishan Rd, Jinan ShanDong Province"
  15: };
  16: Customer[] customerArray = new Customer[] { customerFoo, customerBar };
  17: IEnumerable<Customer> customerCollection = customerArray;
  18: IList<Customer> customerList = customerArray.ToList<Customer>(); 
  19:  
  20: Serialize<Customer[]>(customerArray, @"E:\Customer.Array.xml");
  21: Serialize<IEnumerable<Customer>>( customerCollection, @"E:\Customer.GenericIEnumerable.xml");
  22: Serialize<IList<Customer>>( customerList, @"E:\Customer.GenericIList.xml);

我們最終發現,雖然創建DataContractSerializer對象使用的類型不一樣,但是最終序列化生成出來的XML卻是完全一樣的,也就是說DataContractSerializer在序列化這3種類型對象時,采用完全一樣的序列化規則。從下麵的XML的結構和內容中,我們可以總結出下麵3條規則:

  • 根節點的名稱以ArrayOf為前綴,後麵緊跟集合元素類型對應的數據契約名稱;
  • 集合元素對象用數據契約的命名空間作為整個集合契約的命名空間;
  • 每個元素對象按照其數據契約定義進行序列化。
   1: <ArrayOfCustomer xmlns:i="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://www.artech.com/">
   2:     <Customer>
   3:         <ID>8baed181-bcbc-493d-8592-3e08fd5ad1cf</ID>
   4:         <Name>Foo</Name>
   5:         <Phone>8888-88888888</Phone>
   6:         <CompanyAddress>#9981, West Sichuan Rd, Xian Shanxi Province</CompanyAddress>
   7:     </Customer>
   8:     <Customer>
   9:         <ID>2fca9719-4120-430c-9dc2-3ef9dc7dffb1</ID>
  10:         <Name>Bar</Name>
  11:         <Phone>9999-99999999</Phone>
  12:         <CompanyAddress>#3721, Taishan Rd, Jinan ShanDong Province</CompanyAddress>
  13:     </Customer>
  14: </ArrayOfCustomer>

我們從根節點的名稱ArrayOfCustomer,可以看出WCF將這個3個類型的對象IEnumerable<Customer>、IList<Cusomter>和Customer[]都作為Customer數組了。實際上,如果你在定義服務契約的時候,將某個服務操作的參數類型設為IEnumerable<T>或者<IList>,默認導出生成的服務契約中,相應的參數類型就是數組類型。 比如,在同一個服務契約中,我定義了如下3個操作,他們的參數類型分別為IEnumerable<Customer>、IList<Cusomter>和Customer[]。當客戶端通過添加服務引用導出服務契約後,3個操作的參數類型都變成Customer[]了。

   1: [ServiceContract]
   2: public interface ICustomerManager
   3: {
   4:     [OperationContract]
   5:     void AddCustomerArray(Customer[] customers);
   6:     [OperationContract]
   7:     void AddCustomerCollection(IEnumerable<Customer> customers);
   8:     [OperationContract]
   9:     void AddCustomerList(IList<Customer> customers);
  10: }
   1: [ServiceContract]
   2: [ServiceContract]
   3: public interface ICustomerManager
   4: {
   5:     [OperationContract]
   6:     void AddCustomerArray(Customer[] customers);
   7:     [OperationContract]
   8:     void AddCustomerCollection(Customer[] customers);
   9:     [OperationContract]
  10:     void AddCustomerList(Customer[] customers);
  11: }

由於對於DataContractSerializer來說,IEnumerable<Customer>、IList<Cusomter>和Customer[]這3種形式的數據表述方式是等效的,那麼就意味著當客戶端在通過添加服務引用導入服務契約的時候,customers通過Customer[]與通過IList<Cusomter>表示也具有等效性,我們能否讓數組類型變成IList<T>類型呢,畢竟從編程角度來看,它們還是不同的,很多時候使用IList<T>要比直接使用數組方便得多。

答案是肯定的,Vistual Studio允許我們在添加服務引用的時候進行一些定製,其中生成的集合類型和字典集合類型的定製就包含其中。如圖1所示,VS為我們提供了6種不同的集合類型供我們選擇:Array、ArrayList、LinkedList、GenericList、Collection、BindingList。

對於上麵定義的服務契約ICustomerManager,如果在添加服務引用時使用GenericList選項,導入的服務契約的所有操作參數類型全部變成List<Customer>。

   1: [ServiceContract]
   2: public interface ICustomerManager
   3: {
   4:     [OperationContract]
   5:     void AddCustomerArray(List<Customer> customers);
   6:     [OperationContract]
   7:     void AddCustomerCollection(List<Customer> customers);
   8:     [OperationContract]
   9:     void AddCustomerList(List<Customer> customers);
  10: }

clip_image002

圖1 在添加服務引用時指定集合類型

上麵我們介紹了IEnumerable<T>、Array與IList<T>這3種集合類型的序列化規則,這3種集合類型有一個共同的特點,那就是集合類型的申明指明了集合元素的類型。當基於這3種集合類型的DataContractSerializer被創建出來後,由於元素類型已經明確了,所以能夠按照元素類型對應的數據契約的定義進行合理的序列化工作。但是對於不能預先確定元素類型的IEnumerable和IList就不一樣了。

下麵我將演示IEnumerable和IList兩種類型的序列化。在介紹已知類型的時候,我們已經明確了,無論是序列化還是反序列化都需要預先明確對象的真實類型,對於不能預先確定具體類型的情況下,我們需要潛在的類型添加到DataContractSerializer的已知類型列表中,才能保證序列化和反序列化的正常進行。由於創建基於IEnumerable和IList的DataContractSerializer的時候,集合元素類型是不可知的,所以需要將潛在的元素類型添加到DataContractSerializer的已知類型列表中,為此我們使用下麵一個包含已知類型列表參數的Serialize<T>輔助方法進行序列化工作。

   1: public static void Serialize<T>(T instance, string fileName, IList<Type> konwnTypes)
   2: {
   3:     DataContractSerializer serializer = new DataContractSerializer(typeof(T), konwnTypes, int.MaxValue, false, false, null);
   4:     using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8))
   5:     {
   6:         serializer.WriteObject(writer, instance);
   7:     }
   8:     Process.Start(fileName);
   9: }

為了和基於IEnumerable<T>、Array與IList<T>序列化做對比,我采用相同的編程方式,使用相同的數據。

   1: Customer customerFoo = new Customer
   2:      {
   3:          ID             = Guid.NewGuid(),
   4:          Name         = "Foo",
   5:          Phone         = "8888-88888888",
   6:          CompanyAddress     = "#9981, West Sichuan Rd, Xian Shanxi Province"
   7:      }; 
   8:  
   9: Customer customerBar = new Customer
  10: {
  11:     ID         = Guid.NewGuid(),
  12:     Name         = "Bar",
  13:     Phone         = "9999-99999999",
  14:     CompanyAddress     = "#3721, Taishan Rd, Jinan ShanDong Province"
  15: };
  16: Customer[] customers = new Customer[] { customerFoo, customerBar };
  17: IEnumerable customersCollection = customers;
  18: IList customersList = customers.ToList(); 
  19:  
  20: Serialize<IEnumerable>(customersCollection, @"E:\Customer.IEnumerable.xml", new List<Type> { typeof(Customer) });
  21: Serialize<IList>(customersList, @"E:\Customer.IList.xml", new List<Type> { typeof(Customer) });

無論是基於IEnumerable類型,還是基於IList,最終序列化生成的XML都是一樣的。對於IEnumerable和IList,集合元素的類型沒有限製,可以是任何類型的對象,所以根節點的名稱為ArrayOfanyType,每個子節點的名稱為anyType。在真正對具體的元素對象進行序列化的時候,通過反射並借助於已知類型,獲得相應數據契約的定義,並以此為依據進行序列化。

   1: <ArrayOfanyType xmlns:i="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://schemas.microsoft.com/2003/10/Serialization/Arrays">
   2:     <anyType xmlns:d2p1="https://www.artech.com" i:type="d2p1:Customer">
   3:         <d2p1:ID>604cc219-d1fe-4e0c-92c8-83486e13b354</d2p1:ID>
   4:         <d2p1:Name>Foo</d2p1:Name>
   5:         <d2p1:Phone>8888-88888888</d2p1:Phone>
   6:         <d2p1:CompanyAddress>#9981, West Sichuan Rd, Xian Shanxi Province</d2p1:CompanyAddress>
   7:     </anyType>
   8:     <anyType xmlns:d2p1="https://www.artech.com" i:type="d2p1:Customer">
   9:         <d2p1:ID>ef3e2806-789a-4208-974d-a67f8ed92e4c</d2p1:ID>
  10:         <d2p1:Name>Bar</d2p1:Name>
  11:         <d2p1:Phone>9999-99999999</d2p1:Phone>
  12:         <d2p1:CompanyAddress>#3721, Taishan Rd, Jinan ShanDong Province</d2p1:CompanyAddress>
  13:     </anyType>
  14: </ArrayOfanyType>

ArrayOfanyType的含義是任何類型的數組,在.NET中,就是object[]。而實際上,對於服務契約來說,如果某個操作包含有IEnumerable或者IList類型的參數,當該服務契約被客戶端導入後,IEnumerable或者IList參數類型將會自動轉換成object[]。比如對於下麵的服務契約,其導入形式如後麵的代碼所示。當然你可以通過修改服務引用關於輸出集合類型,使參數類型按照你希望的形式輸出(如果先擇GenericList,那麼參數類型將會轉換為List<object>)。

   1: [ServiceContract]
   2: [ServiceKnownType(typeof(Customer))]
   3: public interface ICustomerManager
   4: {
   5:     void AddCustomerCollection(IEnumerable customers);
   6:     [OperationContract]
   7:     void AddCustomerList(IList customers);
   8: }
   1: [ServiceContract]
   2: [ServiceKnownType(typeof(Customer))]
   3: public interface ICustomerManager
   4: {
   5:     void AddCustomerCollection(object[] customers);
   6:     [OperationContract]
   7:     void AddCustomerList(object[]customers);
   8: }

前麵我們基本上都是在介紹基於係統定義集合類型的序列化問題,而在很多情況下,我們會自定義一些集合類型。那麼在WCF下對自定義集合類型具有哪些限製,DataContractSerializer對於自定義集合類型又具有怎樣的序列化規則呢?我們接下來就來討論這些問題。

為了演示基於自定義集合類型的序列化,我定義了下麵一個集合類型:CustomerCollection,表示一組Customer對象的組合。Customer的列表通過IList<Customer>類型成員保存;定義了兩個構造函數,無參構造函數沒有任何實現,另一個則提供Customer對象列表;Add方法方便添加Customer對象成員。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     public class CustomerCollection:IEnumerable<Customer>
   4:     {
   5:         private IList<Customer> _customers = new List<Customer>();
   6:  
   7:         public CustomerCollection(IList<Customer> customers)
   8:         {
   9:             this._customers = customers;
  10:         }
  11:  
  12:         public CustomerCollection()
  13:         { }
  14:  
  15:         public void Add(Customer customer)
  16:         {
  17:             this._customers.Add(customer);
  18:         }
  19:  
  20:         //IEnumerable<Customer> 成員
  21:         public IEnumerator<Customer> GetEnumerator()
  22:         {
  23:             return this._customers.GetEnumerator();
  24:         }
  25:  
  26:         //IEnumerable 成員
  27:         IEnumerator IEnumerable.GetEnumerator()
  28:         {
  29:             return this._customers.GetEnumerator();
  30:         }
  31:     }
  32: }

借助於前麵定義的Serialize<T>輔助方法,對創建出來的CustomerCollection對象進行序列化。

   1: IList<Customer> customers = new List<Customer>
   2: {
   3:     new Customer
   4:     { 
   5:         ID             = Guid.NewGuid(),
   6:         Name             = "Foo",
   7:         Phone         = "8888-88888888",
   8:         CompanyAddress     = "#9981, West Sichuan Rd, Xian Shanxi Province"
   9:     },
  10:     new    Customer
  11:     {
  12:         ID             = Guid.NewGuid(),
  13:         Name             = "Bar",
  14:         Phone         = "9999-99999999",
  15:         CompanyAddress     = "#3721, Taishan Rd, Jinan ShanDong Province"
  16:     }
  17: }; 
  18:  
  19: Serialize<CustomerCollection>(new CustomerCollection(customers), @"e:\customers.xml");

執行上麵的代碼,將會得到下麵一段被序列化生成的XML。通過與上麵生成的XML比較,我們發現基於自定義CustomerCollection對象序列化的XML與基於IEnumerable<Customer>、IList<Customer>和Customer[]的XML完全是一樣的。這是因為CustomerCollection實現了IEnumerable<Customer>接口。所以從數據契約的角度來看待CustomerCollection和IEnumerable<Customer>、IList<Customer>與Customer[],它們是完全等效的。

   1: <ArrayOfCustomer xmlns:i="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://www.artech.com">
   2:     <Customer>
   3:         <ID>504afb71-c765-48c2-97f4-a1100e81ab1e</ID>
   4:         <Name>Foo</Name>
   5:         <Phone>8888-88888888</Phone>
   6:         <CompanyAddress>#9981, West Sichuan Rd, Xian Shanxi Province</CompanyAddress>
   7:     </Customer>
   8:     <Customer>
   9:         <ID>5c1a3469-fc80-4a28-8d17-79f2c849193d</ID>
  10:         <Name>Bar</Name>
  11:         <Phone>9999-99999999</Phone>
  12:         <CompanyAddress>#3721, Taishan Rd, Jinan ShanDong Province</CompanyAddress>
  13:     </Customer>
  14: </ArrayOfCustomer>

既然CustomerCollection和IEnumerable<Customer>、IList<Customer>與Customer[]完全等效,那麼自定義集合類型對於數據契約有什麼意義呢?因為Customer是一個數據契約,IEnumerable<Customer>、IList<Customer>與Customer[]隻能算是數據契約的集合,其本身並不算是一個數據契約。而通過自定義集合類型,我們可以將集合整體定義成一個數據契約,我們基於集合的數據契約稱為集合數據契約(Collection Data Contract)。集合數據契約通過System.Runtime.Serialization.CollectionDataContractAttribute特性定義。我們先來看看CollectionDataContractAttribute的定義。Name、Namepace和IsReference,和DataContractAttrbute中同名屬性具有相同的含義。額外的3個屬性成員分別表示為:

  • ItemName:集合元素的名稱,默認值為集合元素數據契約的名稱
  • KeyName:針對於字典型(Key-Value Pair)集合,表示每個Item的Key的名稱
  • ValueName:針對於字典型(Key-Value Pair)集合,表示每個Item的Value的名稱
   1: [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
   2: public sealed class CollectionDataContractAttribute : Attribute
   3: {
   4:     public CollectionDataContractAttribute();
   5:  
   6:     public string     Name { get; set; }
   7:     public string     Namespace { get; set; } 
   8:     public bool     IsReference { get; set; }   
   9:  
  10:     public string     ItemName { get; set; }
  11:     public string     KeyName { get; set; }
  12:     public string     ValueName { get; set; }
  13: }

我們通過CollectionDataContractAttribute對CustomerCollection進行如下的改造,將集合契約名稱指定為CustomerList,集合元素名稱為CustomerEntry,重寫命名空間https://www.artech.com/collection。後麵的XML反映出了改造的成果。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [CollectionDataContract(Name = "CustomerList", ItemName = "CustomerEntry", Namespace = "https://www.artech.com/collection/")]
   4:     public class CustomerCollection:IEnumerable<Customer>
   5:     {
   6:         //省略成員
   7:     }
   8: }
   1: <CustomerList xmlns:i="https://www.w3.org/2001/XMLSchema-instance" xmlns:d1p1="https://www.artech.com" xmlns="https://www.artech.com/collection/">
   2:     <CustomerEntry>
   3:         <d1p1:ID>d780f6c4-7d3d-427b-a6e7-10220c77d349</d1p1:ID>
   4:         <d1p1:Name>Foo</d1p1:Name>
   5:         <d1p1:Phone>8888-88888888</d1p1:Phone>
   6:         <d1p1:CompanyAddress>#9981, West Sichuan Rd, Xian Shanxi Province</d1p1:CompanyAddress>
   7:     </CustomerEntry>
   8:     <CustomerEntry>
   9:         <d1p1:ID>d0e82e4d-702a-40cd-a58f-540eb8213578</d1p1:ID>
  10:         <d1p1:Name>Bar</d1p1:Name>
  11:         <d1p1:Phone>9999-99999999</d1p1:Phone>
  12:         <d1p1:CompanyAddress>#3721, Taishan Rd, Jinan ShanDong Province</d1p1:CompanyAddress>
  13:     </CustomerEntry>
  14: </CustomerList>

1、默認無參數構造函數的必要性

我想有的讀者可能會覺得奇怪,在定義CustomerCollection的時候,為什麼加上一個默認無參的構造函數,這不是多此一舉嗎?你根本就沒有添加任何代碼在此構造函數中。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     public class CustomerCollection:IEnumerable<Customer>
   4:     {
   5:         //其他成員
   6:         public CustomerCollection()
   7:         { }
   8:     }
   9: }

實際上,這個默認無參的構造函數並非隨意加上去的,這是為了保證DataContractSerializer正常工作所必須的。在使用DataContractSerializer對某個對象進行序列化的時候,我們不能光看到序列化本身,還要看到與之相對的操作:反序列化。如果不同時保證正常的反序列化,序列化實際上沒有太大的意義。而默認無參的構造函數的存在就是為了反序列化服務的,因為DataContractSerializer在將XML反序列化成某種類型的對象的時候,需要通過反射調用默認的構造函數創建對象。所以對於CustomerCollection來說,默認的構造函數是必須的。

如果我們將此默認無參的構造函數去掉,運行我們的程序將會拋出如圖2所示的InvalidDataContractException異常。表明沒有默認構造函數的CustomerCollection是不合法的集合契約。

clip_image002[5]

圖2 缺少默認無參數構造函數導致的序列化異常

2、Add方法的必要性

在CustomerCollection類型中,為了更加方便地添加Customer對象到集合中,我定義了Add方法。實際上,此Add方法的意義並非僅僅是一個輔助方法,它和默認構造函數一樣,是集合數據契約所必須的。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     public class CustomerCollection:IEnumerable<Customer>
   4:     {
   5:         //其他成員
   6:         public void Add(Customer customer)
   7:         {
   8:             this._customers.Add(customer);
   9:         }
  10:     }
  11: }

如果我們將此Add方法從CustomerCollection中去掉。會拋出如圖3所示的InvalidDataContractException異常,表明沒有Add方法的CustomerCollection是不合法的集合數據契約。所以在定義集合數據契約的時候,哪怕你不需要,你都必須加上一個空的Add方法。

clip_image002[7]

圖3 缺少Add方法導致的序列化異常

3、簡化自定義集合數據契約定義

為了演示默認構造函數和Add方法對於集合數據契約的必要性,再定義CustomerCollection的實現,僅僅是實現了IEnumerable<Customer>結構,並通過封裝一個IList<Customer>對象實現了IEnumerable<Customer>的方法。實際上,我們隻需要讓CustomerCollection繼承List<Customer>就可以了。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     public class CustomerCollection:List<Customer>
   4:     {
   5:     }
   6: }

IDictionary<TKey,TValue>與Hashtable是一種特殊的集合類型,它的集合元素類型是一個鍵-值對(Key-Value Pair),前者通過範型參數指明了Key和Value的類型,後者則可以使用任何類型,或者說Key和Value的類型都是object。

我們照例通過一個具體的例子看看WCF在通過DataContractSerializer序列化IDictionary<TKey,TValue>與Hashtable對象的時候,采用怎樣的序列化規則。我們使用基於Customer對象IDictionary<TKey,TValue>和Hashtable,Key和Value分別使用Cutomer的ID和Customer對象本身,Customer類型在前麵已經定義了。借助前麵定義的兩個Serialize<T>輔助方法,對表示相同Customer集合的IDictionary<TKey,TValue>與Hashtable對象進行序列化,由於對於Hashtable來說,無法確定集合元素的具體類型,我們需要將Customer類型作為DataContractSerializer的已知類型。

   1: Customer customerFoo = new Customer
   2:     {
   3:         ID             = Guid.NewGuid(),
   4:         Name             = "Foo",
   5:         Phone         = "8888-88888888",
   6:         CompanyAddress     = "#9981, West Sichuan Rd, Xian Shanxi Province"
   7:     }; 
   8:  
   9: Customer customerBar = new Customer
  10:     {
  11:         ID             = Guid.NewGuid(),
  12:         Name             = "Bar",
  13:         Phone         = "9999-99999999",
  14:         CompanyAddress     = "#3721, Taishan Rd, Jinan ShanDong Province"
  15:     }; 
  16:  
  17: IDictionary<Guid, Customer> customerDictionary = new Dictionary<Guid, Customer>();
  18: Hashtable customerHashtable = new Hashtable(); 
  19:  
  20: customerDictionary.Add(customerFoo.ID, customerFoo);
  21: customerDictionary.Add(customerBar.ID, customerBar);
  22:  
  23: customerHashtable.Add(customerFoo.ID, customerFoo);
  24: customerHashtable.Add(customerBar.ID, customerBar); 
  25:  
  26: Serialize<IDictionary<Guid, Customer>>(customerDictionary, @"e:\customers.dictionary.xml");
  27: Serialize<Hashtable>(customerHashtable, @"e:\customers.hashtable.xml",new List<Type>{typeof(Customer)});

我們先來看看IDictionary<Guid, Customer>對象經過序列化會產生怎樣的XML。從下麵的XML,我們可以總結出相應的序列化規則。

  • 根節點名稱為ArrayOfKeyValueOfguidCustomer2af2CULK,原因很簡單。IDictionary<Guid, Customer>的集合元素類型是KeyValyePair<Guid,Customer>,按照基於泛型數據契約的命名,需要加上範型數據契約的名稱和範型類型的哈希值以解決命名衝突,所以KeyValueOfguidCustomer2af2CULK與KeyValyePair<Guid,Customer>類型相匹配,作為KeyValyePair<Guid,Customer>的集合,IDictionary<Guid, Customer>的名稱自然就是ArrayOfKeyValueOfguidCustomer2af2CULK了
  • 每個元素名稱為KeyValueOfguidCustomer2af2CULK,是一個Key和Value節點的組合。Key和Value內容按照相應類型數據契約的定義進行係列化。
   1: <ArrayOfKeyValueOfguidCustomer2af2CULK xmlns:i="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://schemas.microsoft.com/2003/10/Serialization/Arrays">
   2:     <KeyValueOfguidCustomer2af2CULK>
   3:         <Key>a2718c6f-fce4-46df-909b-64a62d30387b</Key>
   4:         <Value xmlns:d3p1="https://www.artech.com/">
   5:             <d3p1:ID>a2718c6f-fce4-46df-909b-64a62d30387b</d3p1:ID>
   6:             <d3p1:Name>Foo</d3p1:Name>
   7:             <d3p1:Phone>8888-88888888</d3p1:Phone>
   8:             <d3p1:CompanyAddress>#9981, West Sichuan Rd, Xian Shanxi Province</d3p1:CompanyAddress>
   9:         </Value>
  10:     </KeyValueOfguidCustomer2af2CULK>
  11:     <KeyValueOfguidCustomer2af2CULK>
  12:         <Key>0f1defe1-59a7-447e-96dc-03b4ae4ba31c</Key>
  13:         <Value xmlns:d3p1="https://www.artech.com/">
  14:             <d3p1:ID>0f1defe1-59a7-447e-96dc-03b4ae4ba31c</d3p1:ID>
  15:             <d3p1:Name>Bar</d3p1:Name>
  16:             <d3p1:Phone>9999-99999999</d3p1:Phone>
  17:             <d3p1:CompanyAddress>#3721, Taishan Rd, Jinan ShanDong Province</d3p1:CompanyAddress>
  18:         </Value>
  19:     </KeyValueOfguidCustomer2af2CULK>
  20: </ArrayOfKeyValueOfguidCustomer2af2CULK>

再來看看Hashtable對象被序列化後的XML。從下麵的XML中可以看出,由於Hashtable與IDictionary<Guid,Customer>是同一數據在CLR類型上的不同表現形式,所以最終序列化出來的結構都是一樣的,不同的僅僅是根節點與集合元素節點的命名而已。由於Hashtable元素的Key和Value沒有類型限製,所以根節點和元素節點的名稱轉換為ArrayOfKeyValueOfanyTypeanyType和KeyValueOfanyTypeanyType,這是很好理解的。

   1: <ArrayOfKeyValueOfanyTypeanyType xmlns:i="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://schemas.microsoft.com/2003/10/Serialization/Arrays">
   2:     <KeyValueOfanyTypeanyType>
   3:         <Key xmlns:d3p1="https://schemas.microsoft.com/2003/10/Serialization/" i:type="d3p1:guid">a2718c6f-fce4-46df-909b-64a62d30387b</Key>
   4:         <Value xmlns:d3p1="https://www.artech.com" i:type="d3p1:Customer">
   5:             <d3p1:ID>a2718c6f-fce4-46df-909b-64a62d30387b</d3p1:ID>
   6:             <d3p1:Name>Foo</d3p1:Name>
   7:             <d3p1:Phone>8888-88888888</d3p1:Phone>
   8:             <d3p1:CompanyAddress>#9981, West Sichuan Rd, Xian Shanxi Province</d3p1:CompanyAddress>
   9:         </Value>
  10:     </KeyValueOfanyTypeanyType>
  11:     <KeyValueOfanyTypeanyType>
  12:         <Key xmlns:d3p1="https://schemas.microsoft.com/2003/10/Serialization/" i:type="d3p1:guid">0f1defe1-59a7-447e-96dc-03b4ae4ba31c</Key>
  13:         <Value xmlns:d3p1="https://www.artech.com" i:type="d3p1:Customer">
  14:             <d3p1:ID>0f1defe1-59a7-447e-96dc-03b4ae4ba31c</d3p1:ID>
  15:             <d3p1:Name>Bar</d3p1:Name>
  16:             <d3p1:Phone>9999-99999999</d3p1:Phone>
  17:             <d3p1:CompanyAddress>#3721, Taishan Rd, Jinan ShanDong Province</d3p1:CompanyAddress>
  18:         </Value>
  19:     </KeyValueOfanyTypeanyType>
  20: </ArrayOfKeyValueOfanyTypeanyType>

從WCF的序列化來講,所有的集合類型都可以看成是數組,無論是上麵介紹的IEnumerable、IEnumerable<T>、IList、IList<T>,還是現在介紹的Hashtable和IDictionary<TKey,TValue>,最終序列化的都是ArrayOfXxx。不過與其他集合類型不同的是,對於服務契約定義,如果操作參數類型為Hashtable和IDictionary<TKey,TValue>,最終在客戶端導入的不再是數組,而是Dictionary<TKey,TValue>,不過前者對應的永遠是Dictionary<object, object>,後者的泛型參數類型可以被成功導入。比如下麵兩段代碼片斷就是相同的服務契約在定義和導入時表現出來的不同形態。

   1: [ServiceContract]
   2: [ServiceKnownType(typeof(Customer))]
   3: public interface ICustomerManager
   4: {
   5:     [OperationContract]
   6:     void AddCustomerHashtable(Hashtable customers);
   7:     [OperationContract]
   8:     void AddCustomerDictionary(IDictionary<Guid, Customer> customers);
   9: }
   1: [ServiceContract] 
   2:  
   3: [ServiceKnownType(typeof(Customer))] 
   4:  
   5: public interface ICustomerManager 
   6:  
   7: { 
   8:  
   9: [OperationContract] 
  10:  
  11: void AddCustomerHashtable(Dictionary<object, object> customers); 
  12:  
  13: [OperationContract] 
  14:  
  15: void AddCustomerDictionary(Dictionary<Guid, Customer> customers); 
  16:  
  17: }

在通過VS添加服務引用的時候,對於一般的集合類型,你可以通過相關的服務引用的設置,選擇你希望生成的集合類型,對於基於字典類型的集合,VS同樣提供了這樣的設置。如圖4所示,對於字典集合類型,我們具有更多的選擇。

clip_image002[9]

圖4 添加服務引用時指定字典集合類型

自定義字典數據契約類型

在上麵,我們通過CollectionDataContractAttribute特性實現了自定義集合數據契約,我們同樣可以通過該特性自定義字典類型的集合數據契約。在下麵的例子中,我們定義了一個直接繼承了Dictionary<Guid,Customer>類型的數據契約。並通過CollectionDataContractAttribute的ItemName、KeyName和ValueName屬性定義了集合元素的名稱,以及集合元素Key和Value的名稱。

   1: namespace Artech.DataContractSerializerDemos
   2: {
   3:     [CollectionDataContract(Name="CustomerCollection",  Namespace="https://www.artech.com/",ItemName="CustomerEntry",  KeyName = "CustomerID", ValueName="Customer")]
   4:     public class CustomerDictionary:Dictionary<Guid,Customer>
   5:     {
   6:     }
   7: }

如果通過下麵的代碼對CustomerDictionary對象進行序列化,最終生成的XML將如下麵所示。

   1: CustomerDictionary customers = new CustomerDictionary();
   2: Customer customerFoo = new Customer
   3:     {
   4:         ID = Guid.NewGuid(),
   5:         Name = "Foo",
   6:         Phone = "8888-88888888",
   7:         CompanyAddress = "#9981, West Sichuan Rd, Xian Shanxi Province"
   8:     }; 
   9:  
  10: Customer customerBar = new Customer
  11:     {
  12:         ID             = Guid.NewGuid(),
  13:         Name             = "Bar",
  14:         Phone         = "9999-99999999",
  15:         CompanyAddress     = "#3721, Taishan Rd, Jinan ShanDong Province"
  16:     };
  17: customers.Add(customerFoo.ID, customerFoo);
  18: customers.Add(customerBar.ID, customerBar);
  19: Serialize<CustomerDictionary>(customers, @"e:\customers.xml");
   1: <CustomerCollection xmlns:i="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://www.artech.com">
   2:     <CustomerEntry>
   3:         <CustomerID>38b5ebb3-654d-4100-b4fc-8614a952b836</CustomerID>
   4:         <Customer>
   5:             <ID>38b5ebb3-654d-4100-b4fc-8614a952b836</ID>
   6:             <Name>Foo</Name>
   7:             <Phone>8888-88888888</Phone>
   8:             <CompanyAddress>#9981, West Sichuan Rd, Xian Shanxi Province</CompanyAddress>
   9:         </Customer>
  10:     </CustomerEntry>
  11:     <CustomerEntry>
  12:         <CustomerID>cda1f0e3-c10e-4e76-affb-6dbf5a3b4381</CustomerID>
  13:         <Customer>
  14:             <ID>cda1f0e3-c10e-4e76-affb-6dbf5a3b4381</ID>
  15:             <Name>Bar</Name>
  16:             <Phone>9999-99999999</Phone>
  17:             <CompanyAddress>#3721, Taishan Rd, Jinan ShanDong Province</CompanyAddress>
  18:         </Customer>
  19:     </CustomerEntry>
  20: </CustomerCollection>

注:部分內容節選自《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 12:04:22

  上一篇:go  阿裏雲雙11訪談之MaxCompute
  下一篇:go  WCF技術剖析之十五:數據契約代理(DataContractSurrogate)在序列化中的作用