数据协定中的集合类型
“集合”指特定类型的项的列表。 在 .NET Framework 中,可以使用数组或者其他各种类型(泛型列表、泛型 BindingList<T>、StringCollection 或 ArrayList)来表示此类列表。 例如,集合可以容纳给定客户的地址列表。 无论这些集合的实际类型是什么,这些集合都称为“列表集合” 。
存在一种特殊形式的集合,该集合表示某一项(“键”)与另一项(“值”)之间的关联。 在 .NET Framework 中,这些集合通过类型(如 Hashtable)和泛型字典来表示。 例如,一个关联集合可能将城市(“键”)映射到它的人口数量(“值”)。 无论这些集合的实际类型是什么,这些集合都称为“字典集合” 。
集合在数据协定模型中受到特殊对待。
实现 IEnumerable 接口的类型(包括数组和泛型集合)被识别为集合。 其中,实现 IDictionary 或泛型 IDictionary<TKey,TValue> 接口的类型是字典集合,其他所有类型是列表集合。
关于集合类型的其他要求(例如,有一个名为 Add
的方法和一个无参数构造函数)在下面各部分详细讨论。 这确保了既可以对集合类型序列化,也可以对其反序列化。 这意味着不直接支持某些集合,例如泛型 ReadOnlyCollection<T>(因为它没有无参数构造函数)。 但是,也可以避开这些限制。有关信息,请参见本主题后面的“使用集合接口类型和只读集合”。
包含在集合中的类型必须是数据协定类型,或者是可序列化的。 有关详细信息,请参阅数据协定序列化程序支持的类型。
有关什么被视为有效集合,什么不被视为有效集合,以及如何序列化集合的详细信息,请参阅本主题“高级集合规则”部分中有关序列化集合的信息。
可互换的集合
同一类型的所有列表集合均被视为具有相同的数据协定(除非它们是使用 CollectionDataContractAttribute 属性自定义的,如本主题后面所述)。 因此,举例而言,下面的数据协定是等效的。
[DataContract(Name = "PurchaseOrder")]
public class PurchaseOrder1
{
[DataMember]
public string customerName;
[DataMember]
public Collection<Item> items;
[DataMember]
public string[] comments;
}
[DataContract(Name = "PurchaseOrder")]
public class PurchaseOrder2
{
[DataMember]
public string customerName;
[DataMember]
public List<Item> items;
[DataMember]
public BindingList<string> comments;
}
<DataContract(Name:="PurchaseOrder")>
Public Class PurchaseOrder1
<DataMember()>
Public customerName As String
<DataMember()>
Public items As Collection(Of Item)
<DataMember()>
Public comments() As String
End Class
<DataContract(Name:="PurchaseOrder")>
Public Class PurchaseOrder2
<DataMember()>
Public customerName As String
<DataMember()>
Public items As List(Of Item)
<DataMember()>
Public comments As BindingList(Of String)
End Class
两个数据协定均产生类似于以下代码的 XML。
<PurchaseOrder>
<customerName>...</customerName>
<items>
<Item>...</Item>
<Item>...</Item>
<Item>...</Item>
...
</items>
<comments>
<string>...</string>
<string>...</string>
<string>...</string>
...
</comments>
</PurchaseOrder>
举例而言,集合的可互换性使您可以使用一个针对服务器性能而优化的集合类型,以及一个旨在绑定到客户端上的用户界面组件的集合类型。
与列表集合类似,具有相同键和值类型的所有字典集合都被视为具有相同的数据协定(除非通过 CollectionDataContractAttribute 属性自定义)。
就集合等效性而言,只有数据协定类型才有意义,.NET 类型并无意义。 也就是说,如果 Type1 和 Type2 具有等效的数据协定,那么 Type1 的集合将被视为与 Type2 的集合等效。
非泛型集合被视为与 Object
类型的泛型集合具有相同的数据协定。 (例如, ArrayList 的数据协定与 List<T> 的泛型 Object
是相同的。)
使用集合接口类型和只读集合
集合接口类型(IEnumerable、 IDictionary、泛型 IDictionary<TKey,TValue>或从这些接口派生的接口)也被视为具有与实际集合类型的集合数据协定等效的集合数据协定。 因此,可以声明被序列化为集合接口类型的类型,并且结果与使用实际集合类型时的结果相同。 例如,下面的数据协定是等效的。
[DataContract(Name="Customer")]
public class Customer1
{
[DataMember]
public string customerName;
[DataMember]
public Collection<Address> addresses;
}
[DataContract(Name="Customer")]
public class Customer2
{
[DataMember]
public string customerName;
[DataMember]
public ICollection<Address> addresses;
}
<DataContract(Name:="Customer")>
Public Class Customer1
<DataMember()>
Public customerName As String
<DataMember()>
Public addresses As Collection(Of Address)
End Class
<DataContract(Name:="Customer")>
Public Class Customer2
<DataMember()>
Public customerName As String
<DataMember()>
Public addresses As ICollection(Of Address)
End Class
序列化期间,当声明的类型是接口时,使用的实际实例类型可以是实现该接口的任何类型。 前面讨论的限制(具有无参数构造函数和 Add
方法)不适用。 例如,即使您不能直接声明泛型 ReadOnlyCollection<T> 类型的数据成员,仍然可以将 Customer2 中的地址设置为 Address 的泛型 ReadOnlyCollection<T>的实例。
反序列化期间,当声明的类型是接口时,序列化引擎会选择实现所声明的接口的类型,并且该类型会实例化。 已知的类型机制(已在数据协定已知类型中介绍)在这里没有任何效果;类型的选择内置于 WCF 中。
自定义集合类型
您可以通过使用 CollectionDataContractAttribute 属性来自定义集合类型,该属性具有几种用法。
请注意,自定义集合类型会危害集合的可互换性,因此通常建议尽量避免应用此特性。 有关此问题的详细信息,请参阅本主题后面的“高级集合规则”一节。
集合数据协定命名
如 Data Contract Names中所述,命名集合类型的规则与命名常规数据协定类型的规则类似,但存在一些重要的区别:
使用 CollectionDataContractAttribute 属性(而不是 DataContractAttribute 属性)来自定义名称。 CollectionDataContractAttribute 属性 (Attribute) 也有
Name
和Namespace
属性 (Property)。当未应用 CollectionDataContractAttribute 属性时,集合类型的默认名称和命名空间依赖于集合内包含的类型的名称和命名空间。 它们不受集合类型本身的名称和命名空间的影响。 有关示例,请参见以下类型。
public CustomerList1 : Collection<string> {} public StringList1 : Collection<string> {}
两个类型的数据协定名称都是“ArrayOfstring”,而不是“CustomerList1”或“StringList1”。 这意味着在根级别序列化其中任何一个类型都将产生与下面的代码类似的 XML。
<ArrayOfstring>
<string>...</string>
<string>...</string>
<string>...</string>
...
</ArrayOfstring>
选择此命名规则可确保表示字符串列表的任何非自定义类型都具有相同的数据协定和 XML 表示。 这使得集合的可互换性成为可能。 在本例中,CustomerList1 和 StringList1 是完全可互换的。
但是,当应用 CollectionDataContractAttribute 属性时,集合将成为一个自定义的集合数据协定,即使未在该属性 (Attribute) 上设置任何属性 (Property) 也将如此。 之后,集合数据协定的名称和命名空间将取决于集合类型本身。 有关示例,请参见下面的类型。
[CollectionDataContract]
public class CustomerList2 : Collection<string> {}
<CollectionDataContract()>
Public Class CustomerList2
Inherits Collection(Of String)
End Class
序列化后,所产生的 XML 将类似于以下代码:
<CustomerList2>
<string>...</string>
<string>...</string>
<string>...</string>
...
</CustomerList2>
请注意,这不再等效于非自定义类型的 XML 表示。
您可以使用
Name
和Namespace
属性来进一步自定义命名。 请参见下面的类。[CollectionDataContract(Name="cust_list")] public class CustomerList3 : Collection<string> {}
<CollectionDataContract(Name:="cust_list")> Public Class CustomerList3 Inherits Collection(Of String) End Class
所产生的 XML 类似于以下内容。
<cust_list>
<string>...</string>
<string>...</string>
<string>...</string>
...
</cust_list>
有关详细信息,请参阅本主题后面的“高级集合规则”一节。
自定义列表集合中的重复元素名称
列表集合包含重复项。 通常,每个重复项都表示为根据集合中包含的类型的数据协定名称而命名的元素。
在 CustomerList
示例中,集合包含字符串。 字符串基元类型的数据协定名称是“string”,因此重复元素是“<string>”。
但是,通过对 ItemName 属性 (Attribute) 使用 CollectionDataContractAttribute 属性 (Property),可以自定义该重复元素名称。 有关示例,请参见下面的类型。
[CollectionDataContract(ItemName="customer")]
public class CustomerList4 : Collection<string> {}
<CollectionDataContract(ItemName:="customer")>
Public Class CustomerList4
Inherits Collection(Of String)
End Class
所产生的 XML 类似于以下内容。
<CustomerList4>
<customer>...</customer>
<customer>...</customer>
<customer>...</customer>
...
</CustomerList4>
重复元素的命名空间始终与集合数据协定的命名空间相同,如前所述,该命名空间可以使用 Namespace
属性自定义。
自定义字典集合
字典集合实质上是项列表,其中每一项都有一个后面跟随值的键。 与常规列表一样,您可以使用 ItemName 属性更改与重复元素对应的元素名称。
此外,您还可以使用 KeyName 和 ValueName 属性来更改表示键和值的元素名称。 这些元素的命名空间与集合数据协定的命名空间相同。
有关示例,请参见下面的类型。
[CollectionDataContract
(Name = "CountriesOrRegionsWithCapitals",
ItemName = "entry",
KeyName = "countryorregion",
ValueName = "capital")]
public class CountriesOrRegionsWithCapitals2 : Dictionary<string, string> { }
<CollectionDataContract(Name:="CountriesOrRegionsWithCapitals",
ItemName:="entry", KeyName:="countryorregion",
ValueName:="capital")>
Public Class CountriesOrRegionsWithCapitals2
Inherits Dictionary(Of String, String)
End Class
序列化后,所产生的 XML 将类似于以下代码:
<CountriesOrRegionsWithCapitals>
<entry>
<countryorregion>USA</countryorregion>
<capital>Washington</capital>
</entry>
<entry>
<countryorregion>France</countryorregion>
<capital>Paris</capital>
</entry>
...
</CountriesOrRegionsWithCapitals>
有关字典集合的详细信息,请参阅本主题后面的“高级集合规则”一节。
集合和已知类型
如果集合类型是以多态方式来代替其他集合或集合接口的,您不需要将这样的集合类型添加到已知类型中。 例如,如果您声明一个 IEnumerable 类型的数据成员并将其用于发送 ArrayList的一个实例,则无需将 ArrayList 添加到已知类型。
当您以多态方式使用集合来代替非集合类型时,必须将这些集合添加到已知类型。 例如,如果您声明一个 Object
类型的数据成员并将其用于发送 ArrayList的一个实例,则需要将 ArrayList 添加到已知类型中。
这不允许您以多态方式序列化任何等效集合。 例如,当您将 ArrayList 添加到前面示例中的已知类型列表时,这不允许您指定 Array of Object
类,即使该类具有等效的数据协定也是如此。 这与常规的已知类型在非集合类型的序列化方面的行为没有任何不同,但对于集合而言了解这一点尤为重要,因为集合的等效性是很常见的。
在序列化期间,在给定数据协定的任何给定范围内,只能有一个类型是已知的,并且等效集合都有相同的数据协定。 这意味着,在前面的示例中,您不能将 ArrayList 和 Array of Object
都添加到同一范围中的已知类型。 同样,这与已知类型在非集合类型方面的行为是等效的,但对于集合而言,了解这一点尤为重要。
对于集合的内容而言,已知类型可能也是必需的。 例如,如果 ArrayList 实际上包含 Type1
和 Type2
的实例,则应将这两个类型都添加到已知类型中。
下面的示例演示使用集合和已知类型正确构造的对象图 本示例虚构成分稍浓,因为在实际的应用程序中,通常不会将下面的数据成员定义为 Object
,因此不会有任何已知类型/多态性问题。
[DataContract]
public class Employee
{
[DataMember]
public string name = "John Doe";
[DataMember]
public Payroll payrollRecord;
[DataMember]
public Training trainingRecord;
}
[DataContract]
[KnownType(typeof(int[]))] //required because int[] is used polymorphically
[KnownType(typeof(ArrayList))] //required because ArrayList is used polymorphically
public class Payroll
{
[DataMember]
public object salaryPayments = new int[12];
//float[] not needed in known types because polymorphic assignment is to another collection type
[DataMember]
public IEnumerable<float> stockAwards = new float[12];
[DataMember]
public object otherPayments = new ArrayList();
}
[DataContract]
[KnownType(typeof(List<object>))]
//required because List<object> is used polymorphically
//does not conflict with ArrayList above because it's a different scope,
//even though it's the same data contract
[KnownType(typeof(InHouseTraining))] //Required if InHouseTraining can be used in the collection
[KnownType(typeof(OutsideTraining))] //Required if OutsideTraining can be used in the collection
public class Training
{
[DataMember]
public object training = new List<object>();
}
[DataContract]
public class InHouseTraining
{
//code omitted
}
[DataContract]
public class OutsideTraining
{
//code omitted
}
<DataContract()>
Public Class Employee
<DataMember()>
Public name As String = "John Doe"
<DataMember()>
Public payrollRecord As Payroll
<DataMember()>
Public trainingRecord As Training
End Class
<DataContract(), KnownType(GetType(Integer())), KnownType(GetType(ArrayList))>
Public Class Payroll
<DataMember()>
Public salaryPayments As Object = New Integer(11) {}
'float[] not needed in known types because polymorphic assignment is to another collection type
<DataMember()>
Public stockAwards As IEnumerable(Of Single) = New Single(11) {}
<DataMember()>
Public otherPayments As Object = New ArrayList()
End Class
'required because List<object> is used polymorphically
'does not conflict with ArrayList above because it's a different scope,
'even though it's the same data contract
<DataContract(), KnownType(GetType(List(Of Object))),
KnownType(GetType(InHouseTraining)),
KnownType(GetType(OutsideTraining))>
Public Class Training
<DataMember()>
Public training As Object = New List(Of Object)()
End Class
<DataContract()>
Public Class InHouseTraining
'code omitted…
End Class
<DataContract()>
Public Class OutsideTraining
'code omitted…
End Class
在反序列化时,如果声明的类型是集合类型,则无论实际发送的是什么类型,都会实例化所声明的类型。 如果所声明的类型是集合接口,则反序列化程序会选取一个要实例化的类型,而不会考虑该类型是否为已知类型。
此外,在反序列化时,如果声明的类型不是一个集合类型,但要发送的是集合类型,则会在已知类型列表之中选取一个匹配的集合类型。 可以在反序列化时将集合接口类型添加到已知类型列表中。 在这种情况下,反序列化引擎会再次选取要实例化的类型。
集合和 NetDataContractSerializer 类
当正在使用 NetDataContractSerializer 类时,非数组的非自定义集合类型(无 CollectionDataContractAttribute 属性)会失去其特殊意义。
用 SerializableAttribute 属性标记的非自定义集合类型仍然可以由 NetDataContractSerializer 类根据 SerializableAttribute 属性或 ISerializable 接口规则来序列化。
自定义的集合类型、集合接口以及数组仍然被视为集合,即使当正在使用 NetDataContractSerializer 类时也将如此。
集合与架构
所有等效的集合在 XML 架构定义 (XSD) 语言架构中都具有相同的表示。 因此,您通常不会在所生成的客户端代码中获得与服务器上的集合类型相同的集合类型。 例如,服务器可能使用具有 Integer 数据成员的泛型 List<T> 的数据协定,但是在生成的客户端代码中,该数据成员可能成为整数数组。
字典集合标记有特定于 WCF 的架构批注,该批注表示这些集合是字典;否则,这些集合将不能与包含具有键和值的项的简单列表区分开来。 有关如何在数据协定架构中表示集合的准确说明,请参阅 Data Contract Schema Reference。
默认情况下,不会为导入的代码中的非自定义集合生成类型。 列表集合类型的数据成员是作为数组导入的,字典集合类型的数据成员是作为泛型字典导入的。
但是,对于自定义集合,将生成单独的类型,这样的类型会标记有 CollectionDataContractAttribute 属性。 (架构中的自定义集合类型是这样的类型:不使用默认的命名空间、名称、重复元素名称或者键/值元素名称)。这些类型是派生自泛型 List<T>(对于列表类型而言)和泛型字典(对于字典类型而言)的空类型。
例如,您可能在服务器上有以下类型。
[DataContract]
public class CountryOrRegion
{
[DataMember]
public Collection<string> officialLanguages;
[DataMember]
public List<DateTime> holidays;
[DataMember]
public CityList cities;
[DataMember]
public ArrayList otherInfo;
}
public class Person
{
public Person(string fName, string lName)
{
this.firstName = fName;
this.lastName = lName;
}
public string firstName;
public string lastName;
}
public class PeopleEnum : IEnumerator
{
public Person[] _people;
// Enumerators are positioned before the first element
// until the first MoveNext() call.
int position = -1;
public PeopleEnum(Person[] list)
{
_people = list;
}
public bool MoveNext()
{
position++;
return (position < _people.Length);
}
public void Reset()
{
position = -1;
}
public object Current
{
get
{
try
{
return _people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
}
[CollectionDataContract(Name = "Cities", ItemName = "city", KeyName = "cityName", ValueName = "population")]
public class CityList : IDictionary<string, int>, IEnumerable<System.Collections.Generic.KeyValuePair<string, int>>
{
private Person[] _people = null;
public bool ContainsKey(string s) { return true; }
public bool Contains(string s) { return true; }
public bool Contains(KeyValuePair<string, int> item) { return (true); }
public void Add(string key, int value) { }
public void Add(KeyValuePair<string, int> keykValue) { }
public bool Remove(string s) { return true; }
public bool TryGetValue(string d, out int i)
{
i = 0; return (true);
}
/*
[TypeConverterAttribute(typeof(SynchronizationHandlesTypeConverter))]
public ICollection<string> SynchronizationHandles {
get { return (System.Collections.Generic.ICollection<string>) new Stack<string> (); }
set { }
}*/
public ICollection<string> Keys
{
get
{
return (System.Collections.Generic.ICollection<string>)new Stack<string>();
}
}
public int this[string s]
{
get
{
return 0;
}
set
{
}
}
public ICollection<int> Values
{
get
{
return (System.Collections.Generic.ICollection<int>)new Stack<string>();
}
}
public void Clear() { }
public void CopyTo(KeyValuePair<string, int>[] array, int index) { }
public bool Remove(KeyValuePair<string, int> item) { return true; }
public int Count { get { return 0; } }
public bool IsReadOnly { get { return true; } }
IEnumerator<KeyValuePair<string, int>>
System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, int>>.GetEnumerator()
{
return (IEnumerator<KeyValuePair<string, int>>)new PeopleEnum(_people); ;
}
public IEnumerator GetEnumerator()
{
return new PeopleEnum(_people);
}
}
<DataContract()>
Public Class CountryOrRegion
<DataMember()>
Public officialLanguages As Collection(Of String)
<DataMember()>
Public holidays As List(Of DateTime)
<DataMember()>
Public cities As CityList
<DataMember()>
Public otherInfo As ArrayList
End Class
Public Class Person
Public Sub New(ByVal fName As String, ByVal lName As String)
Me.firstName = fName
Me.lastName = lName
End Sub
Public firstName As String
Public lastName As String
End Class
Public Class PeopleEnum
Implements IEnumerator
Public _people() As Person
' Enumerators are positioned before the first element
' until the first MoveNext() call.
Private position As Integer = -1
Public Sub New(ByVal list() As Person)
_people = list
End Sub
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
position += 1
Return position < _people.Length
End Function
Public Sub Reset() Implements IEnumerator.Reset
position = -1
End Sub
Public ReadOnly Property Current() As Object Implements IEnumerator.Current
Get
Try
Return _people(position)
Catch e1 As IndexOutOfRangeException
Throw New InvalidOperationException()
End Try
End Get
End Property
End Class
<CollectionDataContract(Name:="Cities",
ItemName:="city",
KeyName:="cityName",
ValueName:="population")>
Public Class CityList
Implements IDictionary(Of String, Integer), IEnumerable(Of System.Collections.Generic.KeyValuePair(Of String, Integer))
Private _people() As Person = Nothing
Public Function ContainsKey(ByVal s As String) As Boolean Implements IDictionary(Of String, Integer).ContainsKey
Return True
End Function
Public Function Contains(ByVal s As String) As Boolean
Return True
End Function
Public Function Contains(ByVal item As KeyValuePair(Of String, Integer)) As Boolean Implements IDictionary(Of String, Integer).Contains
Return (True)
End Function
Public Sub Add(ByVal key As String,
ByVal value As Integer) Implements IDictionary(Of String, Integer).Add
End Sub
Public Sub Add(ByVal keykValue As KeyValuePair(Of String, Integer)) Implements IDictionary(Of String, Integer).Add
End Sub
Public Function Remove(ByVal s As String) As Boolean Implements IDictionary(Of String, Integer).Remove
Return True
End Function
Public Function TryGetValue(ByVal d As String,
<System.Runtime.InteropServices.Out()> ByRef i As Integer) _
As Boolean Implements IDictionary(Of String, Integer).TryGetValue
i = 0
Return (True)
End Function
Public ReadOnly Property Keys() As ICollection(Of String) Implements IDictionary(Of String, Integer).Keys
Get
Return CType(New Stack(Of String)(), System.Collections.Generic.ICollection(Of String))
End Get
End Property
Default Public Property Item(ByVal s As String) As Integer Implements IDictionary(Of String, Integer).Item
Get
Return 0
End Get
Set(ByVal value As Integer)
End Set
End Property
Public ReadOnly Property Values() As ICollection(Of Integer) Implements IDictionary(Of String, Integer).Values
Get
Return CType(New Stack(Of String)(), System.Collections.Generic.ICollection(Of Integer))
End Get
End Property
Public Sub Clear() Implements IDictionary(Of String, Integer).Clear
End Sub
Public Sub CopyTo(ByVal array() As KeyValuePair(Of String, Integer),
ByVal index As Integer) Implements IDictionary(Of String, Integer).CopyTo
End Sub
Public Function Remove(ByVal item As KeyValuePair(Of String, Integer)) As Boolean Implements IDictionary(Of String, Integer).Remove
Return True
End Function
Public ReadOnly Property Count() As Integer Implements IDictionary(Of String, Integer).Count
Get
Return 0
End Get
End Property
Public ReadOnly Property IsReadOnly() As Boolean Implements IDictionary(Of String, Integer).IsReadOnly
Get
Return True
End Get
End Property
Private Function IEnumerable_GetEnumerator() As IEnumerator(Of KeyValuePair(Of String, Integer)) _
Implements System.Collections.Generic.IEnumerable(Of System.Collections.Generic.KeyValuePair(Of String, Integer)).GetEnumerator
Return CType(New PeopleEnum(_people), IEnumerator(Of KeyValuePair(Of String, Integer)))
End Function
Public Function GetEnumerator() As IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return New PeopleEnum(_people)
End Function
End Class
当导出并再次导入架构时,生成的客户端代码与下面类似(为便于阅读,显示的是字段而不是属性)。
[DataContract]
public class CountryOrRegion2
{
[DataMember]
public string[] officialLanguages;
[DataMember]
public DateTime[] holidays;
[DataMember]
public Cities cities;
[DataMember]
public object[] otherInfo;
}
[CollectionDataContract(ItemName = "city", KeyName = "cityName", ValueName = "population")]
public class Cities : Dictionary<string, int> { }
<DataContract()>
Public Class CountryOrRegion2
<DataMember()>
Public officialLanguages() As String
<DataMember()>
Public holidays() As DateTime
<DataMember()>
Public cities As Cities
<DataMember()>
Public otherInfo() As Object
End Class
<CollectionDataContract(ItemName:="city", KeyName:="cityName", ValueName:="population")>
Public Class Cities
Inherits Dictionary(Of String, Integer)
End Class
您可能想要在生成的代码中使用与默认类型不同的类型。 例如,为了使数据成员易于绑定到用户界面组件,您可能想对它们使用泛型 BindingList<T> ,而不使用常规数组。
若要选择要生成的集合类型,请在导入架构时,将要使用的集合类型的列表传递到 ReferencedCollectionTypes 对象的 ImportOptions 属性。 这些类型称为“引用的集合类型” 。
当引用泛型类型时,这些类型必须要么是完全开放式泛型,要么是完全封闭式泛型。
注意
当使用 Svcutil.exe 工具时,可以使用 /collectionType 命令行开关(简写形式是 /ct)来完成此引用。 请记住,还必须使用 /reference 开关(简写形式是 /r)指定引用的集合类型的程序集。 如果此类型是泛型,则它后面必须跟有反引号和泛型参数的数目。 不要将反引号 (`) 与单引号字符 (‘) 混淆。 你可以通过多次使用 /collectionType 开关来指定多个引用的集合类型。
例如,使所有列表作为泛型 List<T>导入。
svcutil.exe MyService.wsdl MyServiceSchema.xsd /r:C:\full_path_to_system_dll\System.dll /ct:System.Collections.Generic.List`1
导入任意集合时,都会扫描这一引用的集合类型列表,如果找到一个最佳匹配集合,则会将该集合用作数据成员类型(对于非自定义集合)或可从中派生其他类型的基类型(对于自定义集合)。 字典只能与字典匹配,列表只能与列表匹配。
例如,如果您将泛型 BindingList<T> 和 Hashtable 添加到引用类型列表,则前面示例的生成客户端代码将类似于以下代码:
[DataContract]
public class CountryOrRegion3
{
[DataMember]
public BindingList<string> officialLanguages;
[DataMember]
public BindingList<DateTime> holidays;
[DataMember]
public Cities cities;
[DataMember]
public BindingList<object> otherInfo;
}
[CollectionDataContract(ItemName = "city", KeyName = "cityName", ValueName = "population")]
public class Cities3 : Hashtable { }
<DataContract()>
Public Class CountryOrRegion3
<DataMember()>
Public officialLanguages As BindingList(Of String)
<DataMember()>
Public holidays As BindingList(Of DateTime)
<DataMember()>
Public cities As Cities
<DataMember()>
Public otherInfo As BindingList(Of Object)
End Class
<CollectionDataContract(ItemName:="city",
KeyName:="cityName",
ValueName:="population")>
Public Class Cities3
Inherits Hashtable
End Class
您可以将集合接口类型指定为引用的集合类型的一部分,但不能指定无效的集合类型(例如,没有 Add
方法或公共构造函数的类型)。
封闭式泛型被视为最佳匹配 (非泛型类型被视为与 Object
的封闭式泛型等效)。 例如,如果 List<T> 的泛型 DateTime、泛型 BindingList<T> (开放式泛型)和 ArrayList 类型是引用的集合类型,则会生成以下代码:
[DataContract]
public class CountryOrRegion4
{
[DataMember]
public string[] officialLanguages;
[DataMember]
public DateTime[] holidays;
[DataMember]
public Cities cities;
[DataMember]
public object[] otherInfo;
}
[CollectionDataContract(ItemName = "city", KeyName = "cityName", ValueName = "population")]
public class Cities4 : Dictionary<string, int> { }
<DataContract()>
Public Class CountryOrRegion4
<DataMember()>
Public officialLanguages() As String
<DataMember()>
Public holidays() As DateTime
<DataMember()>
Public cities As Cities
<DataMember()>
Public otherInfo() As Object
End Class
<CollectionDataContract(ItemName:="city",
KeyName:="cityName",
ValueName:="population")>
Public Class Cities4
Inherits Dictionary(Of String, Integer)
End Class
对于列表集合,只支持下面的表中的情况。
引用类型 | 引用类型所实现的接口 | 示例 | 类型被视为: |
---|---|---|---|
非泛型或封闭式泛型(任意多个参数) | 非泛型 | MyType : IList 或 MyType<T> : IList 其中 T= int |
Object 的封闭式泛型(例如, IList<object> ) |
非泛型或封闭式泛型(可以有任意多个参数,而且这些参数不是必须与集合类型匹配) | 封闭式泛型 | MyType : IList<string> 或 MyType<T> : IList<string> 其中 T=int |
封闭式泛型(例如 IList<string> ) |
可以有任意多个参数的封闭式泛型 | 使用该类型的任何一个参数的开放式泛型 | MyType<T,U,V> : IList<U> 其中 T= int ,U=string ,V=bool |
封闭式泛型(例如 IList<string> ) |
具有一个参数的开放式泛型 | 使用该类型的参数的开放式泛型 | MyType<T> : IList<T> ,T 是开放式的 |
开放式泛型(例如 IList<T> ) |
如果类型实现多个列表集合接口,则下列限制适用:
如果类型针对不同的类型多次实现泛型 IEnumerable<T> (或它的派生接口),则该类型不会被视为有效的引用集合类型,因此会被忽略。 即使有些实现是无效的或者使用开放式泛型,也将如此。 例如,实现 IEnumerable<T> 的泛型
int
以及 T 的泛型 IEnumerable<T> 的类型绝不会用作int
或其他任何类型的引用集合,无论该类型是否具有接受Add
的int
方法和/或接受类型 T 的参数的Add
方法,都将如此。如果该类型实现一个泛型集合接口和 IList,则该类型将绝不会用作引用的集合类型,除非该泛型集合接口是 Object类型的封闭式泛型。
对于字典集合,只支持下面的表中的情况。
引用类型 | 引用类型所实现的接口 | 示例 | 类型被视为 |
---|---|---|---|
非泛型或封闭式泛型(任意多个参数) | IDictionary | MyType : IDictionary 或 MyType<T> : IDictionary 其中 T=int |
封闭式泛型 IDictionary<object,object> |
封闭式泛型(任意多个参数) | IDictionary<TKey,TValue>,封闭式 | MyType<T> : IDictionary<string, bool> 其中 T=int |
封闭式泛型(例如 IDictionary<string,bool> ) |
封闭式泛型(任意多个参数) | 泛型 IDictionary<TKey,TValue>,键或值中的一个是封闭式的,另一个是开放式的,并使用类型的某个参数 | MyType<T,U,V> : IDictionary<string,V> ,其中 T=int ,U=float ,V=bool 或 MyType<Z> : IDictionary<Z,bool> ,其中 Z=string |
封闭式泛型(例如 IDictionary<string,bool> ) |
封闭式泛型(任意多个参数) | 泛型 IDictionary<TKey,TValue>,键和值均是开放式的,且每个都使用类型的一个参数 | MyType<T,U,V> : IDictionary<V,U> 其中 T=int ,U=bool ,V=string |
封闭式泛型(例如 IDictionary<string,bool> ) |
开放式泛型(两个参数) | 开放式泛型 IDictionary<TKey,TValue>,按显示顺序使用类型的两个泛型参数 | MyType<K,V> : IDictionary<K,V> ,K 和 V 均是开放式的 |
开放式泛型(例如 IDictionary<K,V> ) |
如果类型同时实现 IDictionary 和泛型 IDictionary<TKey,TValue>,则只将考虑泛型 IDictionary<TKey,TValue> 。
不支持引用部分泛型类型。
不允许重复,例如,不能将 List<T> 的泛型 Integer
和 Integer
的泛型集合都添加到 ReferencedCollectionTypes中,因此这会使得当在架构中找到整数列表时无法确定使用其中的哪一个。 只有当架构中存在暴露重复问题的类型时,才会检测重复项。 例如,如果导入的架构不包含整数列表,则在 List<T> 中可以同时具有 Integer
的泛型 Integer
和 ReferencedCollectionTypes的泛型集合,但是两者都没有任何效果。
高级集合规则
序列化集合
下面是集合序列化规则的列表:
允许组合集合类型(具有集合的集合)。 交错数组被视为集合的集合。 不支持多维数组。
字节数组和 XmlNode 数组是特殊的数组类型,将被视为基元,而不是集合。 序列化字节数组会产生单个包含一个 Base64 编码数据块的 XML 元素,而不是为每个字节都生成一个单独的元素。 有关如何处理 XmlNode 数组的详细信息,请参阅数据协定中的 XML 和 ADO.NET 类型。 当然,这些特殊类型本身可以参与集合:字节数组的数组会产生多个 XML 元素,其中每个元素都包含一个 Base64 编码数据块。
如果 DataContractAttribute 属性应用于集合类型,则该类型会被视为常规数据协定类型,而不是集合。
如果集合类型实现 IXmlSerializable 接口,下列规则适用(假定类型为
myType:IList<string>, IXmlSerializable
):如果声明类型为
IList<string>
,则将类型序列化为列表。如果声明类型为
myType
,则将该声明类型序列化为IXmlSerializable
。如果该声明类型为
IXmlSerializable
,则该声明类型将序列化为IXmlSerializable
,但前提是将myType
添加到已知类型的列表。
使用下表中显示的方法对集合进行序列化和反序列化。
集合类型实现 | 序列化时调用的方法 | 反序列化时调用的方法 |
---|---|---|
泛型 IDictionary<TKey,TValue> | get_Keys ,get_Values |
泛型 Add |
IDictionary | get_Keys ,get_Values |
Add |
泛型 IList<T> | 泛型 IList<T> 索引器 | 泛型 Add |
泛型 ICollection<T> | 枚举器 | 泛型 Add |
IList | IList 索引器 | Add |
泛型 IEnumerable<T> | GetEnumerator |
一个称为 Add 的非静态方法,它采用一个相应类型(泛型参数的类型或者它的某个基类型)的参数。 必须存在这样的方法,序列化程序才能在序列化和反序列化期间,都将集合类型视为集合。 |
IEnumerable (因此也包括派生自它的 ICollection) | GetEnumerator |
一个名为 Add 的非静态方法,它采用一个 Object 类型的参数。 必须存在这样的方法,序列化程序才能在序列化和反序列化期间,都将集合类型视为集合。 |
上表按优先级从高到低的顺序列出集合接口。 现举例说明这样列出的含义:如果一个类型同时实现 IList 和泛型 IEnumerable<T>,则集合将按照 IList 规则进行序列化和反序列化:
反序列化时,通过首先调用无参数构造函数创建类型的实例来反序列化所有集合,该默认构造函数必须存在,序列化程序才能在序列化和反序列化期间,都将集合类型视为集合。
如果同一泛型集合接口实现多次(例如,如果一个类型既实现 ICollection<T> 的泛型
Integer
,也实现 ICollection<T> 的泛型 String),并且找不到更高优先级的接口,则该集合将不会被视为有效集合。集合类型可以应用 SerializableAttribute 属性,并且可以实现 ISerializable 接口。 两者都将被忽略。 但是,如果类型未充分满足集合类型要求(例如,缺少
Add
方法),则该类型将不会被视为集合类型,因此会使用 SerializableAttribute 属性和 ISerializable 接口来确定类型是否可以序列化。为了自定义集合而向其应用 CollectionDataContractAttribute 属性会去掉上面的 SerializableAttribute 回退机制。 在这种情况下,如果自定义集合不满足集合类型要求,将会引发 InvalidDataContractException 异常。 异常字符串通常包含说明给定的类型为什么不被视为有效集合(无
Add
方法,无无参数构造函数,等等)的信息,因此出于调试目的而应用 CollectionDataContractAttribute 属性通常很有用。
集合命名
以下是集合命名规则的列表:
所有字典集合数据协定以及包含基元类型的列表集合数据协定的默认命名空间都是
http://schemas.microsoft.com/2003/10/Serialization/Arrays
,前提是未使用 Namespace 重写其命名空间。 为此,映射到内置 XSD 类型的类型以及char
、Timespan
和Guid
类型都将被视为基元。包含非基元类型的集合类型的默认命名空间如果未使用 Namespace 重写,则与集合中包含的类型的数据协定命名空间相同。
列表集合数据协定的默认名称如果未使用 Name 重写,则是“ArrayOf”字符串与集合中包含类型的数据协定名称的组合。 例如,某个整数泛型列表的数据协定名称是“ArrayOfint”。 请记住,
Object
的数据协定名称是“anyType”,因此诸如 ArrayList 这样的非泛型列表的数据协定名称是“ArrayOfanyType”。
字典集合数据协定的默认名称如果未使用 Name
重写,则是“ArrayOfKeyValueOf”字符串与键类型的数据协定名称以及值类型的数据协定名称(按此顺序)的组合。 例如,String 和 Integer 的泛型字典的数据协定名称是“ArrayOfKeyValueOfstringint”。 此外,如果键或值类型不是基元类型,则键和值类型的数据协定命名空间的命名空间哈希将会追加到名称的末尾。 有关命名空间哈希的详细信息,请参阅数据协定名称。
每个字典集合数据协定都有一个表示字典中的一项的伴随数据协定。 除“ArrayOf”前缀外,它的名称与字典数据协定相同,并且命名空间也与字典数据协定相同。 例如,对于“ArrayOfKeyValueOfstringint”字典数据协定,“KeyValueofstringint”数据协定表示字典中的一项。 您可以使用 ItemName
属性来自定义该数据协定的名称,具体说明请见下一部分。
Data Contract Names中介绍的泛型类型命名规则完全适用于集合类型,也就是说,你可以使用 Name 内的大括号来表示泛型类型参数。 但是,括号内的数字指泛型参数,而不是指集合中包含的类型。
集合自定义
CollectionDataContractAttribute 属性的下列用法是被禁止的,这些用法会导致 InvalidDataContractException 异常:
向已应用 DataContractAttribute 属性的类型或者它的某个派生类型应用 CollectionDataContractAttribute 属性。
向实现 CollectionDataContractAttribute 接口的类型应用 IXmlSerializable 属性。
向非集合类型应用 CollectionDataContractAttribute 属性。
试图对应用于非字典类型的 KeyName 属性设置 ValueName 或 CollectionDataContractAttribute 。
多态性规则
如前所述,使用 CollectionDataContractAttribute 属性自定义集合可能会影响集合的可互换性。 如果两个自定义集合类型的名称、命名空间、项名称以及键和值名称(如果它们是字典集合)均匹配,则只能被视为等效。
由于自定义,可能意外地在本应使用某个集合数据协定的地方使用另一个集合数据协定。 应避免这种情况。 请参见以下类型。
[DataContract]
public class Student
{
[DataMember]
public string name;
[DataMember]
public IList<int> testMarks;
}
public class Marks1 : List<int> {}
[CollectionDataContract(ItemName="mark")]
public class Marks2 : List<int> {}
<DataContract()>
Public Class Student
<DataMember()>
Public name As String
<DataMember()>
Public testMarks As IList(Of Integer)
End Class
Public Class Marks1
Inherits List(Of Integer)
End Class
<CollectionDataContract(ItemName:="mark")>
Public Class Marks2
Inherits List(Of Integer)
End Class
在本例中,可以将 Marks1
的实例指定给 testMarks
。 但是,不应使用 Marks2
,因为其数据协定不被视为与 IList<int>
数据协定等效。 数据协定名称是“Marks2”而不是“ArrayOfint”,重复元素名称是“<mark>”,而不是“<int>”。
下表中的规则适用于集合的多态指定。
声明的类型 | 指定非自定义集合 | 指定自定义集合 |
---|---|---|
对象 | 协定名称序列化。 | 协定名称序列化。 使用自定义。 |
集合接口 | 协定名称不序列化。 | 协定名称不序列化。 不使用自定义。* |
非自定义集合 | 协定名称不序列化。 | 协定名称序列化。 使用自定义。** |
自定义集合 | 协定名称序列化。 不使用自定义。** | 协定名称序列化。 使用指定类型的自定义。** |
*对于 NetDataContractSerializer 类,在这种情况下使用自定义。 在这种情况下 NetDataContractSerializer 类还将序列化实际类型名称,以便反序列化按预期的方式工作。
**这些情况会产生架构无效的实例,因此应当避免。
在序列化协定名称的情况下,指定的集合类型应当在已知类型列表中。 反之亦然:在不序列化协定名称的情况下,不需要将该类型添加到已知类型列表中。
可以将派生类型的数组指定给基类型数组。 在这种情况下,会针对每个重复元素序列化派生类型的协定名称。 例如,如果类型 Book
派生自类型 LibraryItem
,则您可以将 Book
的数组指定给 LibraryItem
的数组。 这不适用于其他集合类型。 例如,您不能将 Generic List of Book
指定给 Generic List of LibraryItem
。 但可以指定包含 Generic List of LibraryItem
实例的 Book
。 在使用数组和非数组的情况下, Book
都应当在已知类型列表中。
集合和对象引用保存
当序列化程序在保存对象引用的模式下工作时,对象引用保存也适用于集合。 具体而言,会为整个集合以及集合中包含的各个项保存对象标识。 对于字典,会为键/值对对象以及各个键和值对象保存对象标识。