一、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: }
图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是不合法的集合契约。
图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方法。
图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所示,对于字典集合类型,我们具有更多的选择。
图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)