[爱心链接:拯救一个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<!--CRLF-->
2: {
<!--CRLF-->
3: [DataContract(Namespace="http://www.artech.com/")]<!--CRLF-->
4: public class Customer<!--CRLF-->
5: {
<!--CRLF-->
6: [DataMember(Order = 1)]
<!--CRLF-->
7: public Guid ID<!--CRLF-->
8: { get; set; }
<!--CRLF-->
9:
<!--CRLF-->
10: [DataMember(Order=2)]
<!--CRLF-->
11: public string Name<!--CRLF-->
12: { get; set; }
<!--CRLF-->
13:
<!--CRLF-->
14: [DataMember(Order = 3)]
<!--CRLF-->
15: public string Phone<!--CRLF-->
16: { get; set; }
<!--CRLF-->
17:
<!--CRLF-->
18: [DataMember(Order = 4)]
<!--CRLF-->
19: public string CompanyAddress<!--CRLF-->
20: { get; set; }
<!--CRLF-->
21: }
<!--CRLF-->
22: }
<!--CRLF-->
现在我通过我们前面定义的范型Serialize<T>对以下3种不同类型的集合对象进行序列化:IEnumerable<Customer>、IList<Cusomter>和Customer[]。
1: Customer customerFoo = new Customer<!--CRLF-->
2: {
<!--CRLF-->
3: ID = Guid.NewGuid(),
<!--CRLF-->
4: Name = "Foo",<!--CRLF-->
5: Phone = "8888-88888888",<!--CRLF-->
6: CompanyAddress = "#9981, West Sichuan Rd, Xian Shanxi Province"<!--CRLF-->
7: };
<!--CRLF-->
8:
<!--CRLF-->
9: Customer customerBar = new Customer<!--CRLF-->
10: {
<!--CRLF-->
11: ID = Guid.NewGuid(),
<!--CRLF-->
12: Name = "Bar",<!--CRLF-->
13: Phone = "9999-99999999",<!--CRLF-->
14: CompanyAddress = "#3721, Taishan Rd, Jinan ShanDong Province"<!--CRLF-->
15: };
<!--CRLF-->
16: Customer[] customerArray = new Customer[] { customerFoo, customerBar };<!--CRLF-->
17: IEnumerable<Customer> customerCollection = customerArray;
<!--CRLF-->
18: IList<Customer> customerList = customerArray.ToList<Customer>();
<!--CRLF-->
19:
<!--CRLF-->
20: Serialize<Customer[]>(customerArray, @"E:\Customer.Array.xml");<!--CRLF-->
21: Serialize<IEnumerable<Customer>>( customerCollection, @"E:\Customer.GenericIEnumerable.xml");<!--CRLF-->
22: Serialize<IList<Customer>>( customerList, @"E:\Customer.GenericIList.xml);
<!--CRLF-->
我们最终发现,虽然创建DataContractSerializer对象使用的类型不一样,但是最终序列化生成出来的XML却是完全一样的,也就是说DataContractSerializer在序列化这3种类型对象时,采用完全一样的序列化规则。从下面的XML的结构和内容中,我们可以总结出下面3条规则:
- 根节点的名称以ArrayOf为前缀,后面紧跟集合元素类型对应的数据契约名称;
- 集合元素对象用数据契约的命名空间作为整个集合契约的命名空间;
- 每个元素对象按照其数据契约定义进行序列化。
1: <ArrayOfCustomer xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.artech.com/"><!--CRLF-->
2: <Customer><!--CRLF-->
3: <ID>8baed181-bcbc-493d-8592-3e08fd5ad1cf</ID><!--CRLF-->
4: <Name>Foo</Name><!--CRLF-->
5: <Phone>8888-88888888</Phone><!--CRLF-->
6: <CompanyAddress>#9981, West Sichuan Rd, Xian Shanxi Province</CompanyAddress><!--CRLF-->
7: </Customer><!--CRLF-->
8: <Customer><!--CRLF-->
9: <ID>2fca9719-4120-430c-9dc2-3ef9dc7dffb1</ID><!--CRLF-->
10: <Name>Bar</Name><!--CRLF-->
11: <Phone>9999-99999999</Phone><!--CRLF-->
12: <CompanyAddress>#3721, Taishan Rd, Jinan ShanDong Province</CompanyAddress><!--CRLF-->
13: </Customer><!--CRLF-->
14: </ArrayOfCustomer><!--CRLF-->
我们从根节点的名称ArrayOfCustomer,可以看出WCF将这个3个类型的对象IEnumerable<Customer>、IList<Cusomter>和Customer[]都作为Customer数组了。实际上,如果你在定义服务契约的时候,将某个服务操作的参数类型设为IEnumerable<T>或者<IList>,默认导出生成的服务契约中,相应的参数类型就是数组类型。 比如,在同一个服务契约中,我定义了如下3个操作,他们的参数类型分别为IEnumerable<Customer>、IList<Cusomter>和Customer[]。当客户端通过添加服务引用导出服务契约后,3个操作的参数类型都变成Customer[]了。
1: [ServiceContract]
<!--CRLF-->
2: public interface ICustomerManager<!--CRLF-->
3: {
<!--CRLF-->
4: [OperationContract]
<!--CRLF-->
5: void AddCustomerArray(Customer[] customers);<!--CRLF-->
6: [OperationContract]
<!--CRLF-->
7: void AddCustomerCollection(IEnumerable<Customer> customers);<!--CRLF-->
8: [OperationContract]
<!--CRLF-->
9: void AddCustomerList(IList<Customer> customers);<!--CRLF-->
10: }
<!--CRLF-->
1: [ServiceContract]
<!--CRLF-->
2: [ServiceContract]
<!--CRLF-->
3: public interface ICustomerManager<!--CRLF-->
4: {
<!--CRLF-->
5: [OperationContract]
<!--CRLF-->
6: void AddCustomerArray(Customer[] customers);<!--CRLF-->
7: [OperationContract]
<!--CRLF-->
8: void AddCustomerCollection(Customer[] customers);<!--CRLF-->
9: [OperationContract]
<!--CRLF-->
10: void AddCustomerList(Customer[] customers);<!--CRLF-->
11: }
<!--CRLF-->
由于对于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]
<!--CRLF-->
2: public interface ICustomerManager<!--CRLF-->
3: {
<!--CRLF-->
4: [OperationContract]
<!--CRLF-->
5: void AddCustomerArray(List<Customer> customers);<!--CRLF-->
6: [OperationContract]
<!--CRLF-->
7: void AddCustomerCollection(List<Customer> customers);<!--CRLF-->
8: [OperationContract]
<!--CRLF-->
9: void AddCustomerList(List<Customer> customers);<!--CRLF-->
10: }
<!--CRLF-->
图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)<!--CRLF-->
2: {
<!--CRLF-->
3: DataContractSerializer serializer = new DataContractSerializer(typeof(T), konwnTypes, int.MaxValue, false, false, null);<!--CRLF-->
4: using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8))<!--CRLF-->
5: {
<!--CRLF-->
6: serializer.WriteObject(writer, instance);
<!--CRLF-->
7: }
<!--CRLF-->