Язык

Типы коллекций в контрактах данных

Коллекция — это список элементов определенного типа. В .NET Framework такие списки можно представить с помощью массивов или различных других типов (универсальный список, универсальныйBindingList<T>StringCollection, илиArrayList). Например, коллекция может содержать список адресов для данного клиента. Эти коллекции называются коллекциями списков независимо от их фактического типа.

Специальная форма коллекции существует, представляющая связь между одним элементом (ключом) и другим (значением). В .NET Framework они представлены такими типами, как Hashtable и универсальный словарь. Например, коллекция ассоциаций может сопоставить город ("ключ") с его населением ("значение"). Эти коллекции называются коллекциями словарей независимо от их фактического типа.

Коллекции получают специальное обращение в модели контракта данных.

Типы, реализующие IEnumerable интерфейс, включая массивы и универсальные коллекции, распознаются как коллекции. Из них типы, реализующие IDictionary или универсальные IDictionary<TKey,TValue> интерфейсы, являются коллекциями словарей, а все остальные — коллекциями списков.

Дополнительные требования к типам коллекций, таким как метод с именем Add и конструктором без параметров, подробно рассматриваются в следующих разделах. Это гарантирует, что типы коллекций можно сериализовать и десериализировать. Это означает, что некоторые коллекции не поддерживаются напрямую, например generic 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 и generic List<T> of Object совпадают.)

Использование типов интерфейсов коллекций и коллекций Read-Only

Типы интерфейсов коллекции (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 ), не применяются. Например, можно задать адреса в Customer2 как экземпляр Generic ReadOnlyCollection<T> типа Address, даже если вы не можете напрямую объявить член данных типа Generic ReadOnlyCollection<T>.

Во время десериализации, когда объявленный тип является интерфейсом, модуль сериализации выбирает тип, реализующий объявленный интерфейс, и тип создается. Механизм известных типов (описанный в известных типах контракта данных) не действует здесь; Выбор типа встроен в WCF.

Настройка типов коллекций

Вы можете настроить типы коллекций с помощью атрибута CollectionDataContractAttribute , который имеет несколько вариантов использования.

Обратите внимание, что настройка типов коллекций скомпрометирует взаимозаменяемость коллекции, поэтому обычно рекомендуется избегать применения этого атрибута по возможности. Дополнительные сведения об этой проблеме см. в разделе "Дополнительные правила сбора" далее в этом разделе.

Именование контракта данных коллекции

Правила именования типов коллекций аналогичны правилам именования обычных типов контрактов данных, как описано в именах контрактов данных, хотя существуют некоторые важные различия.

  • Атрибут CollectionDataContractAttribute используется для настройки имени вместо атрибута DataContractAttribute . Атрибут CollectionDataContractAttribute также имеет Name и Namespace свойства.

  • 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 коллекция становится настраиваемым контрактом данных сбора, даже если свойства не заданы в атрибуте. Имя и пространство имен контракта данных коллекции зависят от типа самой коллекции. Пример см. в следующем примере.

[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>".

Однако с помощью свойства атрибута ItemNameCollectionDataContractAttribute можно настроить это повторяющееся имя элемента. Пример см. в следующем примере.

[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). Из-за этого обычно не получается тот же тип коллекции в созданном клиентском коде, что и на сервере. Например, сервер может использовать контракт данных с универсальным элементом List<T> целочисленных данных, но в созданном клиентском коде тот же член данных может стать массивом целых чисел.

Коллекции словарей помечены заметкой схемы WCF, которая указывает на то, что они являются словарями; в противном случае они неотличимы от простых списков, содержащих записи с ключом и значением. Точное описание того, как коллекции представлены в схеме контракта данных, см. в справочнике по схеме контракта данных.

По умолчанию типы не создаются для не настроенных коллекций в импортированном коде. Элементы данных типов коллекций списков импортируются в виде массивов, а элементы данных типов коллекций словаря импортируются как универсальный словарь.

Однако для настраиваемых коллекций создаются отдельные типы, помеченные атрибутом CollectionDataContractAttribute . (Настраиваемый тип коллекции в схеме — это тип, который не использует пространство имен по умолчанию, имя, повторяющееся имя элемента, или имена элементов key/value.) Эти типы являются пустыми типами, производными от обобщённых 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>DateTimeof , generic BindingList<T> (open generic) и 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> (или производные интерфейсы) несколько раз для разных типов, тип не считается допустимым типом коллекции, на который ссылается ссылка, и игнорируется. Это верно, даже если некоторые реализации недопустимы или используют открытые универсальные шаблоны. Например, тип, реализующий Generic IEnumerable<T> из int и Generic IEnumerable<T> из T, никогда не будет использоваться в качестве ссылочной коллекции int или любого другого типа, независимо от того, имеет ли тип метод Add, принимающий int, или метод Add, принимающий параметр типа T, или тот и другой.

  • Если тип реализует общий интерфейс коллекции, а также 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, но ни то ни другое не оказывает какого-либо эффекта.

Продвинутые правила сбора

Сериализация коллекций

Ниже приведен список правил сбора данных для сериализации.

  • Объединение типов коллекций (включая коллекции внутри коллекций) допускается. Зубчатые массивы (jagged arrays) рассматриваются как коллекции коллекций. Многомерные массивы не поддерживаются.

  • Массивы байтов и массивы XmlNode являются специальными типами, которые рассматриваются как примитивы, а не коллекции. Сериализация массива байтов приводит к тому, что один XML-элемент содержит фрагмент данных в кодировке Base64, а не отдельный элемент для каждого байта. Дополнительные сведения о том, как обрабатывается массив 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 Общее добавление
IDictionary get_Keys, get_Values Add
Общий IList<T> Универсальный IList<T> индексатор Общее добавление
Общий ICollection<T> Энумератор Общее добавление
IList IList Индексатор Add
Общий IEnumerable<T> GetEnumerator Нестатический метод, называемый Add, который принимает один параметр соответствующего типа (тип универсального параметра или одного из его базовых типов). Такой метод должен существовать, чтобы сериализатор обрабатывал тип коллекции как коллекцию во время и сериализации, и десериализации.
IEnumerable (и, таким образом, ICollection, производный от него) GetEnumerator Метод называется Add, принимает один параметр типа Object. Такой метод должен существовать, чтобы сериализатор обрабатывал тип коллекции как коллекцию во время и сериализации, и десериализации.

В предыдущей таблице перечислены интерфейсы коллекции в порядке убывания приоритета. Это означает, что, например, если тип реализует и IList и универсальный IEnumerable<T>, то коллекция сериализуется и десериализуется в соответствии с правилами IList:

  • При десериализации все коллекции десериализуются путем создания экземпляра типа через вызов конструктора без параметров, который необходим, чтобы сериализатор мог рассматривать тип коллекции как коллекцию во время сериализации и десериализации.

  • Если один и тот же универсальный интерфейс коллекции реализуется более одного раза (например, если тип реализует Generic ICollection<T> of Integer и Generic ICollection<T> of String) и не найден интерфейс с более высоким приоритетом, то эта коллекция не считается допустимой.

  • Типы коллекций могут иметь к ним примененный атрибут SerializableAttribute и реализовывать интерфейс ISerializable. Оба из них игнорируются. Однако если тип не полностью соответствует требованиям к типу коллекции (например, Add метод отсутствует), тип не считается типом коллекции, поэтому SerializableAttribute атрибут и ISerializable интерфейс используются для определения возможности сериализации типа.

  • Применение атрибута CollectionDataContractAttribute к коллекции для настройки удаляет предыдущий SerializableAttribute резервный механизм. Если настраиваемая коллекция не соответствует требованиям типа коллекции, генерируется исключение InvalidDataContractException. Строка исключения часто содержит сведения, объясняющие, почему данный тип не считается допустимой коллекцией (нет метода Add, без параметрического конструктора и т. д.), поэтому часто полезно применять атрибут CollectionDataContractAttribute для целей отладки.

Именование коллекции

Ниже приведен список правил именования коллекций:

  • Пространство имен по умолчанию для всех контрактов данных коллекции словарей, а также для контрактов данных коллекции списков, содержащих примитивные типы, — это http://schemas.microsoft.com/2003/10/Serialization/Arrays, если не переопределено с помощью пространства имен. Типы, которые соответствуют встроенным типам XSD, а также типы char, Timespan и Guid, считаются примитивами для этой цели.

  • Пространство имен по умолчанию для типов коллекций, содержащих непримитивные типы, если оно не переопределено с помощью Namespace, совпадает с пространством имен, указанным в контракте данных типа, содержащегося в коллекции.

  • Имя по умолчанию для контрактов данных коллекций списка, если оно не переопределено с помощью параметра Name, представляет собой строку, состоящую из "ArrayOf" в сочетании с именем контракта данных типа, содержащегося в коллекции. Например, имя контракта данных для универсального списка целых чисел — ArrayOfint. Помните, что имя контракта данных для Object — «anyType», поэтому имя контракта данных для не обобщенных списков, таких как ArrayList, — «ArrayOfanyType».

Имя по умолчанию для контрактов на сбор данных словаря, если это не переопределено с использованием Name, является строкой "ArrayOfKeyValueOf", соединенной с именем контракта данных типа ключа, за которым следует имя контракта данных типа значения. Например, имя контракта данных для универсального словаря строк и целого числа — ArrayOfKeyValueOfstringint. Кроме того, если типы ключа или значения не являются примитивными типами, к имени добавляется хэш пространства имен пространств имен контракта данных для соответствующих типов ключа и значения. Дополнительные сведения о хэшах пространства имен см. в разделе "Имена контрактов данных".

Каждый контракт сбора данных словаря содержит контракт данных-компаньона, представляющий одну запись в словаре. Его имя совпадает с контрактом данных словаря, за исключением префикса ArrayOf, а его пространство имен совпадает с контрактом данных словаря. Например, для контракта данных словаря ArrayOfKeyValueOfstringint контракт данных KeyValueofstringint представляет одну запись в словаре. Имя этого контракта данных можно настроить с помощью ItemName свойства, как описано в следующем разделе.

Правила именования универсальных типов, как описано в именах контрактов данных, полностью применяются к типам коллекций; То есть можно использовать фигурные скобки в имени, чтобы указать параметры универсального типа. Однако числа в фигурных скобках ссылаются на универсальные параметры, а не типы, содержащиеся в коллекции.

Настройка коллекции

Следующие виды использования атрибута 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> данных. Имя контракта данных — Mark2, а не ArrayOfint, а повторяющееся имя элемента —< mark>, а не< int>.

Правила в следующей таблице применяются к полиморфным назначениям коллекций.

Объявленный тип Назначение не настроенной коллекции Назначение настраиваемой коллекции
Объект Имя контракта подвергается сериализации. Имя контракта подвергается сериализации.

Используется кастомизация.
Интерфейс коллекции Имя контракта не сериализуется. Имя контракта не сериализуется.

Настройка не используется.*
Неперсонализированная коллекция Имя контракта не сериализуется. Имя контракта подвергается сериализации.

Используется кастомизация.
Настраиваемая коллекция Имя контракта подвергается сериализации. Настройка не используется.** Имя контракта подвергается сериализации.

Используется настройка назначенного типа.**

*С классом NetDataContractSerializer в данном случае используется настройка. Класс NetDataContractSerializer также сериализует фактическое имя типа в данном случае, поэтому десериализация работает должным образом.

**Эти случаи приводят к недопустимым экземплярам схемы и поэтому следует избегать.

В случаях, когда имя контракта сериализуется, назначенный тип коллекции должен находиться в списке известных типов. Обратное также верно: в случаях, когда имя не сериализуется, добавление типа в список известных типов не требуется.

Массиву базового типа можно присвоить массив производного типа. В данном случае название контракта для производного типа сериализуется для каждого повторяющегося элемента. Например, если тип Book является производным от типа LibraryItem, можно присвоить массив Book массиву LibraryItem. Это не относится к другим типам коллекций. Например, нельзя назначить Generic List of Book на Generic List of LibraryItem. Однако можно назначить объект Generic List of LibraryItem, который содержит Book экземпляры. В случае массива и в случае, когда нет массива, Book должен находиться в списке известных типов.

Сохранение ссылок на коллекции и объекты

Если сериализатор работает в режиме сохранения ссылок на объекты, сохранение ссылок на объекты также применяется к коллекциям. В частности, идентичность объекта сохраняется как для всей коллекции, так и для отдельных элементов, содержащихся в коллекции. Для словарей идентификатор объекта сохраняется как для объектов пары "ключ-значение", так и для отдельных объектов ключей и значений.

См. также