数据协定中的集合类型

集合是特定类型的项的列表。 在 .NET Framework 中,可以使用数组或多种其他类型(泛型列表、BindingList<T>StringCollectionArrayList)来表示此类列表。 例如,集合可以保存给定客户的地址列表。 这些集合不论其实际类型如何,都称为列表集合

存在一种特殊的集合形式,表示一个项(“key”)和另一项(“值”)之间的关联。 在 .NET Framework 中,这些由 Hashtable 类型和泛型字典表示。 例如,关联集合可能会将城市(“key”)映射到其人口(“value”)。 这些 集合称为字典集合,无论其实际类型如何。

集合在数据协定模型中受到特殊对待。

实现 IEnumerable 接口的类型(包括数组和泛型集合)可识别为集合。 其中,实现 IDictionary 或泛型 IDictionary<TKey,TValue> 接口的类型是字典集合;所有其他类型都是列表集合。

以下各节详细介绍了有关集合类型的其他要求,例如调用 Add 方法和无参数构造函数。 这可确保可以序列化和反序列化集合类型。 这意味着某些集合不受直接支持,例如泛型 ReadOnlyCollection<T> (因为它没有无参数构造函数)。 但是,有关规避这些限制的信息,请参阅本主题后面的“使用集合接口类型和 Read-Only 集合”部分。

集合中包含的类型必须是数据协定类型,或者可序列化。 有关详细信息,请参阅 数据协定序列化程序支持的类型

有关什么是有效集合以及集合的序列化方式的详细信息,请参阅本主题的“高级集合规则”部分中有关序列化集合的信息。

可互换集合

同一类型的所有列表集合都被视为具有相同的数据协定(除非使用 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 是相同的。)

使用集合接口类型和只读集合

集合接口类型(IEnumerableIDictionary泛型 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 方法)不适用。 例如,可以将 Customer2 中的地址设置为通用 ReadOnlyCollection<T> 地址的实例,即使不能直接声明泛型 ReadOnlyCollection<T>类型的数据成员。

在反序列化期间,当声明的类型为接口时,序列化引擎会选择实现声明接口的类型,并实例化该类型。 已知类型机制( 数据协定已知类型中所述)在此处不起作用;类型的选择内置于 WCF 中。

自定义集合类型

可以使用具有多个用途的属性 CollectionDataContractAttribute 来自定义集合类型。

请注意,自定义集合类型会损害集合的可互换性,因此通常建议尽量避免应用此属性。 有关此问题的详细信息,请参阅本主题后面的“高级收集规则”部分。

集合数据协定命名

命名集合类型的规则类似于命名常规数据协定类型的规则,如 数据协定名称中所述,尽管存在一些重要的差异:

两种类型的数据协定名称都是“ArrayOfstring”,而不是“CustomerList1”或“StringList1”。 这意味着,在根级别序列化其中任一类型会生成类似于以下代码的 XML。

<ArrayOfstring>
    <string>...</string>
    <string>...</string>
    <string>...</string>
    ...
</ArrayOfstring>

选择此命名规则以确保表示字符串列表的任何非自定义类型具有相同的数据协定和 XML 表示形式。 这使得集合可互换性成为可能。 在此示例中,CustomerList1 和 StringList1 是完全可互换的。

但是,应用属性时 CollectionDataContractAttribute ,即使未对属性设置任何属性,集合也会成为自定义的集合数据协定。 然后,集合数据协定的名称和命名空间取决于集合类型本身。 有关示例,请参见下面的类型。

[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 表示形式。

  • 可以使用NameNamespace属性进一步自定义命名。 请参见下面的类。

    [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 属性上的 CollectionDataContractAttribute 属性自定义此重复元素名称。 有关示例,请参见下面的类型。

[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 名称。

此外,还可以使用 KeyNameValueName 属性更改表示键和值的元素名称。 这些元素的命名空间与集合数据协定的命名空间相同。

有关示例,请参见下面的类型。

[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 类具有等效的数据协定,你也不能将其分配给 Array of Object 类。 这与非集合类型的常规已知类型在序列化时的行为没有区别,但在集合的情况下了解这一点尤其重要,因为集合行为等价的情况非常常见。

在序列化期间,给定数据协定的任何给定范围中只能知道一种类型,等效集合都具有相同的数据协定。 这意味着,在前面的示例中,不能在同一范围内同时将ArrayListArray of Object添加到已知类型。 同样,这相当于非集合类型的已知类型行为,但对于集合来说,了解这些行为尤为重要。

对于集合的内容而言,已知类型可能也是必需的。 例如,如果ArrayList实际上包含Type1Type2的实例,则这两种类型都应添加到已知类型中。

以下示例演示使用集合和已知类型正确构造的对象图。 该示例有点复杂,因为在实际应用程序中,通常不会将以下数据成员 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 的架构注释进行标记,指示它们是字典;否则,它们与包含具有键和值的条目的简单列表不区分。 有关数据协定架构中如何表示集合的确切说明,请参阅 数据协定架构参考

默认情况下,不会为导入代码中的非自定义集合生成类型。 列表集合类型的数据成员作为数组导入,字典集合类型的数据成员作为泛型字典导入。

但是,对于自定义集合,将生成单独的类型,并用 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

导入任何集合时,会扫描引用的集合类型列表,找到时将使用最佳匹配的集合,如果是非自定义集合,则作为数据成员类型使用;如果是自定义集合,则作为派生的基类型使用。 字典只能与字典进行匹配,而列表只能与列表进行匹配。

例如,如果将 Generic 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的封闭泛型)。 例如,如果类型Generic List<T> of DateTime、Generic 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

where T= int
Object 的封闭式泛型(例如, IList<object>
非泛型或封闭泛型(不一定与集合类型匹配的任意数量的参数) 封闭式泛型 MyType : IList<string>



MyType<T> : IList<string> where T=int
闭合泛型(例如 IList<string>
可以有任意多个参数的封闭式泛型 使用该类型的任何一个参数的开放式泛型 MyType<T,U,V> : IList<U>

where T=int, U=string, V=bool
闭合泛型(例如 IList<string>
具有一个参数的开放式泛型 使用该类型的参数的开放式泛型 MyType<T> : IList<T>,T 处于打开状态 打开泛型 (例如, IList<T>

如果类型实现多个列表集合接口,则适用以下限制:

  • 如果某一类型为不同类型多次实现泛型接口IEnumerable<T>(或其派生接口),则该类型不被视为有效的引用集合类型,并且将被忽略。 即使某些实现无效或使用开放泛型,也是如此。 例如,实现泛型IEnumerable<T>int和泛型IEnumerable<T>的类型永远不会用作int的引用集合或任何其他类型的集合,无论该类型是否有一个接受Add的方法int或一个接受 T 类型参数的方法Add,或同时有这两种方法。

  • 如果该类型实现了泛型集合接口以及IList,那么该类型永远不会被用作引用的集合类型,除非泛型集合接口是类型Object的闭合泛型。

对于字典集合,仅支持下表中的类型。

引用的类型 引用类型所实现的接口 示例: 类型被视为
非泛型或封闭式泛型(任意多个参数) IDictionary MyType : IDictionary



MyType<T> : IDictionary where T=int
封闭式泛型 IDictionary<object,object>
闭合泛型(任意数量的参数) IDictionary<TKey,TValue>,已结束 MyType<T> : IDictionary<string, bool> where T=int 闭合泛型(例如 IDictionary<string,bool>
闭合泛型(任意数量的参数) 泛型 IDictionary<TKey,TValue>,键或值中的一个是封闭式的,另一个是开放式的,并使用类型的某个参数 MyType<T,U,V> : IDictionary<string,V> where T=int, U=float,V=bool



MyType<Z> : IDictionary<Z,bool> where Z=string
闭合泛型 (例如, IDictionary<string,bool>
闭合泛型(任意数量的参数) 泛型 IDictionary<TKey,TValue>,键和值均是开放式的,且每个都使用类型的一个参数 MyType<T,U,V> : IDictionary<V,U> where 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>的泛型IntegerInteger的泛型集合到ReferencedCollectionTypes,因为这样一来,在架构中发现整数列表时,就无法确定该使用哪一个。 只有在架构中存在某种类型暴露重复项问题时,重复项才会被检测到。 例如,如果导入的架构不包含整数列表,则在 List<T> 中可以同时具有 Integer 的泛型 IntegerReferencedCollectionTypes的泛型集合,但是两者都没有任何效果。

高级集合规则

序列化集合

下面是序列化的集合规则列表:

  • 允许组合集合类型(具有集合的集合)。 交错数组被视为集合的集合。 不支持多维数组。

  • 字节数组和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_Keysget_Values 泛型 Add
IDictionary get_Keysget_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 属性应用于集合以进行自定义,将移除先前的回退机制。 相反,如果自定义集合不符合集合类型要求, InvalidDataContractException 则会引发异常。 异常字符串通常包含一些信息,说明为什么某个类型不被视为有效集合(没有Add方法,没有参数构造函数等),因此,应用CollectionDataContractAttribute属性进行调试通常很有用。

集合命名

下面是集合命名规则的列表:

  • 所有字典集合数据契约以及包含基元类型的列表集合数据契约的默认命名空间是http://schemas.microsoft.com/2003/10/Serialization/Arrays,除非使用Namespace进行覆盖。 为此,映射到内置 XSD 类型的类型以及 charTimespanGuid 类型都将被视为基元。

  • 包含非基元类型的集合类型的默认命名空间如果未使用 Namespace 重写,则与集合中包含的类型的数据协定命名空间相同。

  • 列表集合数据协定的默认名称如果未使用 Name 重写,则是“ArrayOf”字符串与集合中包含类型的数据协定名称的组合。 例如,整数泛型列表的数据协定名称为“ArrayOfint”。 请记住,数据协定名称 Object 为“anyType”,因此非泛型列表的数据协定名称类似于 ArrayList “ArrayOfanyType”。

字典集合数据协定的默认名称如果未使用 Name重写,则是“ArrayOfKeyValueOf”字符串与键类型的数据协定名称以及值类型的数据协定名称(按此顺序)的组合。 例如,String 和 Integer 泛型字典的数据协定名称为“ArrayOfKeyValueOfstringint”。 此外,如果键或值的类型不是原始类型,则会将键和值类型数据契约命名空间的哈希值追加到名称中。 有关命名空间哈希的详细信息,请参阅 数据协定名称

每个字典集合数据协定都有一个配套数据协定,表示字典中的一个条目。 其名称与字典数据协定相同,“ArrayOf”前缀除外,其命名空间与字典数据协定的名称相同。 例如,对于“ArrayOfKeyValueOfstringint”字典数据协定,“KeyValueofstringint”数据协定表示字典中的一个条目。 可以使用属性 ItemName 自定义此数据协定的名称,如下一部分所述。

泛型类型命名规则,如 数据协定名称中所述,完全适用于集合类型;也就是说,可以使用 Name 中的大括号来指示泛型类型参数。 但是,大括号中的数字是指泛型参数,而不是集合中包含的类型。

集合自定义

以下 CollectionDataContractAttribute 属性的使用是被禁止的,并会导致 InvalidDataContractException 异常:

多态性规则

如前所述,使用 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 类还会序列化实际类型名称,因此反序列化按预期工作。

**这些情况会导致架构无效的实例,因此应避免。

在序列化协定名称的情况下,分配的集合类型应位于已知类型列表中。 相反的情况也属实:如果名称未序列化,则不需要将类型添加到已知类型列表。

可以将派生类型的数组指定给基类型数组。 在这种情况下,会针对每个重复元素序列化派生类型的协定名称。 例如,如果某个类型派生自该类型BookLibraryItem,则可以将数组Book分配给一个数组LibraryItem。 这不适用于其他集合类型。 例如,不能将 Generic List of Book 分配给 Generic List of LibraryItem。 但可以指定包含 Generic List of LibraryItem 实例的 Book 。 在数组和非数组情况下, Book 应位于已知类型列表中。

集合和对象引用保存

当序列化程序在保留对象引用的模式下运行时,对象引用保留也适用于集合。 具体而言,将为集合中包含的整个集合和单个项保留对象标识。 对于字典,将为键/值对对象和单个键和值对象保留对象标识。

另请参阅