在Net中将实现了IEnumerable接口的所有类型(包括数组和泛型)都称之为集合类型把其中实现了IDictionary接口或泛型IDictionary接口的集合类型称为字典集合剩下的其他集合类型为列表集合 集合的数据契约(协定)缺省名称 缺省情况下WCF框架对集合类型是内建支持的也就说你不需要应用任何属性就可以将集合应用在数据契约(协定)中但前提是集合中的元素必须是应用了DataContractAttribute属性或者是可序列化的类型这时数据契约(协定)名称和命名空间就依赖集合中包含的元素的类型的名称和命名空间了它们不受集合类型本身的名称和命名空间的影响 缺省集合类型数据契约(协定)的格式是(不包括+) 列表集合 名称ArrayOf+集合中包含的元素类型 循环元素名称集合中包含的元素类型 字典集合 名称ArrayOfKeyValueOf+集合中Key的类型+集合中包含的对象类型 循环元素名称KeyValueOf+集合中Key的类型+集合中包含的对象类型 例如 MyCollection : IList{…}的数据契约名称就是ArrayOfint MyCollection : ICollection{…}的数据契约名称就是ArrayOfint MyDictionary : Dictionary{…}的数据契约名称就是ArrayOfKeyValueOfintint MyCollection : ArrayList{…}的数据契约名称就是ArrayOfanyType MyDictionary : Dictionary{…}的数据契约名称就是ArrayOfKeyValueOfintanyType 注意如果是object的话使用的是anyType因为在Schema中所有类型的基类是anyType 如果集合是应用于某个数据契约类型中时那么它的名称将是字段名称如下面Customer的定义以及序列化后的表示 [DataContract] public class Customer { [DataMember] public List<string> addresses = new List<string> {BeijingShangHai }; [DataMember] public Dictionary<int object> telephones = new Dictionary<int object> { { } { } }; } <Customer xmlns:i=instance xmlns=> <addresses xmlns:dp=> <dp:string>Beijing</dp:string> <dp:string>ShangHai</dp:string> </addresses> <telephones xmlns:dp=> <dp:KeyValueOfintanyType> <dp:Key></dp:Key> <dp:Value xmlns:dp= i:type=dp:string></dp:Value> </dp:KeyValueOfintanyType> <dp:KeyValueOfintanyType> <dp:Key></dp:Key> <dp:Value xmlns:dp= i:type=dp:string></dp:Value> </dp:KeyValueOfintanyType> </telephones> </Customer>
集合的契约等价 在WCF中应用了CollectionDataContractAttribute属性的集合称之为定制数据契约集合否则为非定制数据契约集合不管是非定制数据契约集合还是定制数据契约集合只要它们的数据契约名称和循环元素的名称都相同(如果是字典集合其Key和Value也要是相同的)我们就说它们是等价的由于非定制数据契约集合的数据契约以及循环元素的名称由集合中的类型决定所以非定制数据契约集合的数据契约等价遵守以下几个规则(关于数据契约等价的其他详细信息请参见我以前的文章WCF Data Contract之契约等价) 相同类型的列表集合被认为具有相同的数据契约(协定)例如List和int[]是等价的 具有相同相同键和值类型的所有字典集合也被视为具有相同的数据契约(协定) 接口和实现该接口的具体集合类的数据契约是等价的例如IList和List等价 非泛型集合与Object类型的泛型集合的数据契约等价例如List<Object>与ArrayList等价ArrayList与Object[]也是等价的 这就是为什么上面的示例中实现IList和ICollection的集合类型的数据契约名称都是ArrayOfint的原因例如下面两个数据契约是等价的 [DataContract(Name=Customer)] public class Customer { [DataMember] public string customerName; [DataMember] public Collection<string> addresses; [DataMember] public string[] telephones; }[DataContract(Name=Customer)] public class Customer { [DataMember] public string customerName; [DataMember] public ICollection<string> addresses; [DataMember] public List<string> telephones; } 注意Collection和ICollection是等价的同时string[]和List也是等价的 所以针对Customer的定义我们可以将实现ICollection接口的任何集合类的实例赋给addresses同样我们也可以利用在数据契约中定义集合接口的机制来将WCF不支持的集合类型(如ReadOnlyCollection具体见本文中WCF中对集合类型的要求限制一节)来应用到我们的WCF应用中也就是说我们可以将ReadOnlyCollection的实例赋给Customer的addresses 集合与KnowType 集合类型和非集合类型在多态增加KnowType类型方面是不同的关于非集合方面增加KnowType的详细情况请我以前的文章WCF Data Contract之KnowType集合类型在增加KnowType类型遵守以下规则 集合类型是以多态方式来代替其他集合或集合接口的您不需要将这样的集合类型添加到KnowType类型中例如上例中Customer类中的addresses你可以将List的实例赋值给它而不需要将List增加到KnowType类型列表中 当您以多态方式使用集合来代替非集合类型时则需要将它们添加到已知类型 例如如果您声明一个 Object 类型的数据成员并将其用于发送 ArrayList 的一个实例则需要将 ArrayList 添加到已知类型中 等价的集合只能应用KnowTypeAttribute属性来将其增加到KnowType列表中一次例如不能将ArrayList和Object[]都添加到相同类的KnowType列表中 定制集合的数据契约(协定) 我们可以使用CollectionDataContractAttribute的下列属性来指定集合的数据契约的相关名称及命名空间 Name属性来指定集合数据契约的名称(如果没有使用此属性将使用集合类型的名称) Namespace属性来指定其命名空间 ItemName 属性来指定循环元素的名称 针对字典集合还可以用KeyName和ValueName来指定键和值的名称 例如我们将第一节的例子更改成如下所示 [CollectionDataContract(Name = telephones ItemName = telephone KeyName = Index ValueName = Number)] public class MyDictionary : Dictionary { public new DictionaryEnumerator GetEnumerator() { Dictionary innerObject = new Dictionary { { } { } }; return innerObjectGetEnumerator(); } } 此类将被序列化成 >
对于定制数据契约的集合类型来说前面所述的非定制数据契约的集合等价规则将失效所以要尽量避免使用CollectionDataContractAttribute 集合的反序列化 缺省情况下使用Svcutilexe生成客户端代理时列表集合将反序列化成数组字典集合将反序列化成Dictionary泛型我们也可以通过/collectionType 命令行开关(简写形式是 /ct)来指定我们希望反序列化的集合类型(请记住您还必须使用 /reference 开关(简写形式是 /r)指定引用的集合类型的程序集)如果该类型是泛型则必须在类型后面跟有反引号和泛型参数的数目例如前面的例子中的Customer类可以通过下面的命令在客户端使用List泛型//localhost:/ /r:C:\WINDOWS\MicrosoftNET\Framework\v\Systemdll /ct:SystemCollectionsGenericList` DataContractAttribute和CollectionContractAttribute 对于集合而言WCF框架将隐含地自动的为集合类型应用CollectionDataContractAttribute属性的这就是为什么你不需要为集合应用任何属性就可以在数据契约中使用的原因但要注意 ()如果我们新建的集合类型是继承已有的集合类型如List那么我们就不能对新建的集合类型应用DataContractAttribute否则运行时会抛出InvalidDataContractException但你可以应用CollectionDataContractAttribute来定制集合类型的数据契约例如[DataContract]public class MyList:List{…}的集合定义将抛出异常 ()如果我们新建的集合类型是实现了集合接口例如IListIDictionary的话我们可以对此类型应用DataContractAttribute属性这样的话此类型将作为普通的数据契约类型而不是将其作为集合类型来处理也就是WCF框架将只序列化其中应用了DataMemberAttribute属性的成员当然你也可以不应用任何属性来让系统缺省作为集合类型来处理(你也可以使用CollectionDataContractAttribute来定制数据契约) ()针对应用CollectionDataContractAttribute属性或者缺省不应用任何属性的集合类型如果其内部有应用了DataMemberAttribute的属性或字段在序列化时系统将忽略 WCF中对集合类型的要求限制 不是所有的集合类型都可以在WCF中使用只有满足以下要求才可以使用 该集合类型有一个缺省的构造函数 该集合类型有一个名为Add的方法 这是因为在反序列化集合类型时WCF框架首先调用该集合类型的无参数的构造函数然后通过非静态的Add方法来将循环元素增加到集合中所以以上限制主要是针对反序列化而设定的 集合中的一些高级规则 WCF框架在序列化时支持集合的集合也支持数组的数组(交错数组)但不支持多唯数组 字节数组和 XmlNode 数组是特殊的数组类型将被视为基元而不是集合 序列化字节数组会产生单个包含一个 Base 编码数据块的 XML 元素而不是为每个字节都生成一个单独的元素(笔者认为这是为了性能的考虑才这么处理的) 如果集合类型实现了IXMLSerializable接口假设类型为MyType:IListIXMLSerializable{…}WCF框架将根据在数据契约中声明的类型来进行序列化如果声明的是集(接口)如IList那么该类型将被认为是列表集合来序列化如果声明的是IXMLSerializable那么将按照IXMLSerializable来进行序列化当然需要将该类型加到KnowType类型列表中如果声明的是该类型本身(如MyType)那么将按照IXMLSerializable的规则来进行序列化 在对集合进行序列化时将调用集合类的GetEnumerator 方法来得到集合的内容在反序列化时将首先调用该集合类型的无参数的构造函数然后通过非静态的Add方法来将循环元素增加到集合中(注虽然这与大家在MSDN的帮助文档中看到的不同认为字典集合将调用get_Keys和get_Values以及IList将调用索引器但笔者使用VS验证时没有得到以上方法被调用的结论所以笔者认为是MSDN文档滞后或有误如果各位看官能得到和MSDN吻合的结论麻烦告诉一声) 如果集合类型同时应用了Serialized属性或实现了ISerializable接口WCF框架将忽略它们但是如果集合类型不满足集合类型要求(例如缺少Add)方法那么将按照Serialized或ISerializable来处理但如果你对该集合同时应用了CollectionDataContract属性而且又不满足集合要求那么将抛出InvalidDataContractException而不是按照Serialized或ISerializable来处理 不能向实现了IXmlSerializable接口的类型使用CollectionDataContractAttribute属性否则会抛出InvalidDataContractException向非集合应用CollectionDataContractAttribute属性以及非字典集合指定KeyName或者ValueName属性也都将抛出此异常 |