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

时间:2021-03-02 15:18:19

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

[爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道《天天山海经》为此录制的节目视频(苏州话)]]在.NET中,所有的集合都实现了IEnumerable接口,比如Array、Hashtable、ArrayList、Stack、Queue等。有的集合要求元素具有相同的类型,这种集合一般通过泛型的方式定义,它们实现另一个接口IEnumerable<T>(IEnumerable<T>本身继承自IEnumerable),这样的集合有List<T>、Dictionary<TKey,TValue>、Stack<T>、Queue<T>等。基于集合类型的序列化具有一些特殊的规则和行为,在上篇中我们详细介绍了基于泛型数据契约的序列化规则,接下来我们介绍基于集合对象的序列化,以及基于集合类型的服务操作。

一、IEnumerable<T>、Array与IList<T>

一个集合对象能够被序列化的前提是集合中的每个元素都能被序列化,也就是要求元素的类型是一个数据契约(或者是应用了SerialiableAttribute特性)。虽然集合具有各种各样的表现形式,由于其本质就是一组对象的组合,DataContractSerializer在对它们进行序列化的时候,采用的序列化规则和序列化过程中表现出来的行为是相似的。比如我们现在需要通过DataContractSerializer序列化一个Customer对象的集合,Customer类型定义如下。

   1: namespace Artech.DataContractSerializerDemos

   2: {

   3:     [DataContract(Namespace="http://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="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://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: }

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

图1 在添加服务引用时指定集合类型

二、IEnumerable与IList

上面我们介绍了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="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">

   2:     <anyType xmlns:d2p1="http://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="http://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="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://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>

四、CollectionDataContractAttribute

既然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,重写命名空间http://www.artech.com/collection。后面的XML反映出了改造的成果。

   1: namespace Artech.DataContractSerializerDemos

   2: {

   3:     [CollectionDataContract(Name = "CustomerList", ItemName = "CustomerEntry", Namespace = "http://www.artech.com/collection/")]

   4:     public class CustomerCollection:IEnumerable<Customer>

   5:     {

   6:         //省略成员

   7:     }

   8: }

   1: <CustomerList xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:d1p1="http://www.artech.com" xmlns="http://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是不合法的集合契约。

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

图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方法。

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

图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

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="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">

   2:     <KeyValueOfguidCustomer2af2CULK>

   3:         <Key>a2718c6f-fce4-46df-909b-64a62d30387b</Key>

   4:         <Value xmlns:d3p1="http://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="http://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="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">

   2:     <KeyValueOfanyTypeanyType>

   3:         <Key xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/" i:type="d3p1:guid">a2718c6f-fce4-46df-909b-64a62d30387b</Key>

   4:         <Value xmlns:d3p1="http://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="http://schemas.microsoft.com/2003/10/Serialization/" i:type="d3p1:guid">0f1defe1-59a7-447e-96dc-03b4ae4ba31c</Key>

  13:         <Value xmlns:d3p1="http://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所示,对于字典集合类型,我们具有更多的选择。

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

图4 添加服务引用时指定字典集合类型

自定义字典数据契约类型

在上面,我们通过CollectionDataContractAttribute特性实现了自定义集合数据契约,我们同样可以通过该特性自定义字典类型的集合数据契约。在下面的例子中,我们定义了一个直接继承了Dictionary<Guid,Customer>类型的数据契约。并通过CollectionDataContractAttribute的ItemName、KeyName和ValueName属性定义了集合元素的名称,以及集合元素Key和Value的名称。

   1: namespace Artech.DataContractSerializerDemos

   2: {

   3:     [CollectionDataContract(Name="CustomerCollection",  Namespace="http://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="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://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技术剖析系列:

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技术剖析之十六:数据契约的等效性和版本控制


作者:Artech
出处:http://artech.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。