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

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

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

Коллекции подвергаются специальной обработке в модели контракта данных.

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

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

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

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

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

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

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

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

Правила именования типов коллекций подобны правилам именования обычных типов контракта данных, как описано в разделе Data Contract Names. Однако есть некоторые различия.

  • Для пользовательского имени вместо атрибута 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 уже не эквивалентен 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, поэтому повторяющийся элемент был строковым<>.

Однако при применении свойства 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 .

Кроме того, можно изменить имена элементов, представляющих ключ и значение, с помощью свойств 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, которая указывает на то, что они являются словарями; в противном случае они неотличимы от простых списков, содержащих записи с ключом и значением. Подробное описание представлений коллекций в схеме контракта данных см. в статье Data Contract Schema Reference.

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

Тем не менее, для настроенных коллекций создаются отдельные типы, отмеченные атрибутом 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

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

Например, при добавлении универсальных классов 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

or

MyType<T> : IList

где T= int
Закрытый универсальный тип Object (например, IList<object>)
Неуниверсальный или закрытый универсальный (любое количество параметров, которое не обязательно совпадает с типом коллекции) Закрытый универсальный тип MyType : IList<string>

or

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 , и универсальный интерфейс IEnumerable<T> , относящийся к T, никогда не будет использован как коллекция ссылочного типа int или другого типа, независимо от того, имеется ли в типе метод Add , принимающий метод int , или Add , принимающий параметр типа T, или оба.

  • Если тип реализует универсальный интерфейс коллекции, а так же интерфейс IList, тип никогда не будет использован как коллекция ссылочного типа, кроме случая, когда интерфейс универсальной коллекции является закрытым универсальным шаблоном типа Object.

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

Ссылочный тип Интерфейс, реализованный ссылочным типом Пример Тип обрабатывается как
Неуниверсальный или закрытый универсальный (любое количество параметров) IDictionary MyType : IDictionary

or

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

or

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 являются специальными типами массивов, которые обрабатываются как базисные элементы, а не коллекции. В результате сериализации массива байтов получается один 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 :

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

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

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

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

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

Далее приводится список правил именования коллекций.

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

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

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

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

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

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

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

Следующие варианты использования атрибута 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>.

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

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

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

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

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

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

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

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

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

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

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

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

См. также