Типы коллекций в контрактах данных
Под коллекцией понимается список элементов определенного типа. В .NET Framework такие списки могут быть представлены с помощью массивов или других типов (универсальный список, универсальные BindingList, StringCollection или ArrayList). Например, в коллекции может содержаться список адресов конкретного клиента. Такие коллекции называются коллекциями списков, независимо от их фактического типа.
Существует специальная форма коллекции, которая представляет собой сопоставление одного элемента ("ключа") другому ("значению"). В .NET Framework эти формы представлены такими типами, как Hashtable и универсальный словарь. Например, в коллекции сопоставлений город ("ключ") может быть сопоставлен его населению ("значению"). Такие коллекции называются коллекциями-словарями, независимо от их фактического типа.
Коллекции подвергаются специальной обработке в модели контракта данных.
Типы, реализующие интерфейс IEnumerable, включая массивы и универсальные коллекции, распознаются как коллекции. Те из них, которые реализуют интерфейс IDictionary или универсальный интерфейс IDictionary, относятся к коллекциям-словарям; все остальные относятся к коллекциям списков.
Дополнительные требования к типам коллекций, такие как наличие метода с названием Add и конструктора по умолчанию, подробно обсуждаются в следующих разделах. Этим обеспечивается возможность как сериализации, так и десериализации таких типов коллекций. Это означает, что некоторые коллекции не поддерживаются непосредственно, такие как универсальные ReadOnlyCollection (поскольку у них нет конструктора по умолчанию) или Stack (поскольку метод добавления данных в данной коллекции называется Push, а не Add). Тем не менее, информацию о том, как обойти эти ограничения, см. далее в разделе «Использование типов интерфейса и коллекций только для чтения».
Типы, содержащиеся в коллекциях, должны быть контрактными данными, либо сериализуемые другим способом. Дополнительные сведения см. в разделе Типы, поддерживаемые сериализатором контракта данных.
Дополнительные сведения том, что считается, а что не считается действительной коллекцией, а также о том, каким образом сериализуются коллекции, см. информацию о сериализации коллекций в разделе «Расширенные правила коллекций» далее.
Взаимозаменяемые коллекции
Считается, что все коллекции-списки одного типа имеют одинаковый контракт данных (кроме случая, когда они создаются пользователем с помощью атрибута 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;
}
В итоге, от обоих контрактов данных получается код 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 Object являются одинаковыми.)
Использование типов интерфейса коллекции и коллекций только для чтения
Типы интерфейсов коллекции (IEnumerable, IDictionary, универсальный IDictionary или интерфейсы, наследованные от данных интерфейсов) также считаются интерфейсам, имеющими контракты данных коллекций, эквивалентные контрактам данных коллекций текущих типов коллекций. Таким образом, можно объявить сериализуемый тип как тип коллекции интерфейса, и результаты будут такими же, как если бы использовался текущий тип коллекции. Например, указанные ниже контракты данных эквивалентны.
[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;
}
Во время сериализации, если объявленный тип является интерфейсом, текущий используемый тип экземпляра может быть любым типом, реализующим этот интерфейс. Описанные ранее ограничения (имеющие конструктор по умолчанию и метод Add) не применяются. Например, можно задать адреса в Customer2 в экземпляре универсального ReadOnlyCollection значения Address, даже несмотря на то что непосредственно объявить элемент данных универсального класса ReadOnlyCollection невозможно.
Во время десериализации, если объявленный тип является интерфейсом, ядро сериализации выбирает тип, который реализует объявленный интерфейс, и создается экземпляр типа. Механизм известных типов (описанный в Известные типы контрактов данных) в данном случае не дает результата; выбор типа интегрирован в WCF.
Настройка типов коллекции
Настройка типов коллекции может быть выполнена с помощью атрибута CollectionDataContractAttribute, имеющего несколько вариантов использования.
Обратите внимание, что настройка типов коллекции отрицательно влияет на взаимозаменяемость коллекции, т. е. обычно по мере возможности рекомендуется избегать использования данного атрибута. Дополнительные сведения данной проблеме см. в разделе «Расширенные правила коллекции» далее в настоящем разделе.
Именование контракта данных коллекции
Правила именования типов коллекций подобны правилам именования обычных типов контракта данных, как описано в Имена контрактов данных. Однако есть некоторые различия.
Для пользовательского имени вместо атрибута DataContractAttribute используется атрибут CollectionDataContractAttribute. Атрибут 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> {}
При сериализации получаемый код XML подобен приведенному ниже.
<CustomerList2>
<string>...</string>
<string>...</string>
<string>...</string>
...
</CustomerList2>
Необходимо обратить внимание, что полученный XML уже не эквивалентен XML-представлению ненастроенных типов.
Для дополнительной настройки можно использовать свойства Name и Namespace. См. приведенный ниже класс.
[CollectionDataContract(Name="cust_list")] public class CustomerList3 : Collection<string> {}
Получаемый код XML подобен приведенному ниже.
<cust_list>
<string>...</string>
<string>...</string>
<string>...</string>
...
</cust_list>
Дополнительные сведения см. в разделе в разделе «Расширенные правила коллекции» далее.
Настройка имени повторяющегося элемента в коллекциях списков
Коллекции списков содержат повторяющиеся записи. Обычно каждая повторяющаяся запись представляется как элемент с именем, соответствующим имени контракта данных типа, содержащегося в коллекции.
В примерах CustomerList
коллекции содержали строки. Именем контракта данных для типа-примитива строки является "string", поэтому повторяющимся элементом был "<string>".
Однако при применении свойства ItemName к атрибуту CollectionDataContractAttribute данное имя повторяющегося элемента может быть настроено. Например, см. следующий тип.
[CollectionDataContract(ItemName="customer")]
public class CustomerList4 : Collection<string> {}
Получаемый код 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> { }
При сериализации получаемый код 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
}
Во время десериализации, если объявленный тип является коллекцией, экземпляр объявленного типа создается независимо от типа, который был фактически отправлен. Если объявленный тип является интерфейсом коллекции, десериализатор выбирает тип для создания экземпляра независимо от известных типов.
Кроме того, если объявленный тип не является типом коллекции, но при десериализации отправляется тип коллекции, совпадающий тип коллекции извлекается из списка известных типов. Типы интерфейса коллекций можно добавить в список известных типов при десериализации. В таком случае ядро десериализации повторно выбирает тип для создания экземпляра.
Коллекции и класс NetDataContractSerializer
При использовании класса NetDataContractSerializer ненастроенные типы коллекций (без атрибута CollectionDataContractAttribute), не являющиеся массивами, теряют свое особое значение.
Ненастроенные типы коллекций, отмеченные атрибутом SerializableAttribute, все равно могут быть сериализованы с помощью класса NetDataContractSerializer в соответствии с атрибутом SerializableAttribute или правилами интерфейса ISerializable.
Настроенные типы коллекций, интерфейсы коллекций и массивы даже при использовании класса NetDataContractSerializer обрабатываются как коллекции.
Коллекции и схема
Все эквивалентные коллекции имеют одинаковое представление в схеме на языке определения схемы XML (XSD). По этой причине, в коде, созданном клиентом, и в коде, созданном на сервере, тип коллекции обычно отличается. Например, сервер может использовать контракт данных с универсальным List целочисленного элемента данных, а в коде, созданном клиентом, тот же элемент данных может стать массивом целых чисел.
Коллекции-словари отмечаются свойственной WCF аннотацией к схеме, которая показывает, что данные коллекции являются словарями; в противном случае, их невозможно отличить от простых списков, содержащих записи с ключом и значением. Подробное описание представлений коллекций в схеме контракта данных см. в Справочник по схеме контрактов данных.
По умолчанию в импортированном коде типы для ненастроенных коллекций не создаются. Элементы данных коллекций списков импортируются как массивы, а элементы данных коллекций-словарей импортируются как универсальный словарь.
Тем не менее, для настроенных коллекций создаются отдельные типы, отмеченные атрибутом CollectionDataContractAttribute. (Тип настроенной коллекции в схеме является типом, который не использует пространство имен, имя, имя повторяющегося элемента или имена ключа/значения элемента по умолчанию). Данные типы являются пустыми типами, наследованными от универсального List для типов списков и универсальным словарем для типов словаря.
Например, на сервере могут быть следующие типы.
[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);
}
}
При экспорте схемы и ее импорте обратно, созданный клиентом код подобен следующему коду (для простоты чтения вместо свойств показаны поля).
Возможно, пользователь захочет использовать в созданном коде другие типы, отличные от типов по умолчанию. Например, чтобы облегчить привязку элементов данных к компонентам интерфейса, пользователь может использовать для элементов данных универсальный класс BindingList вместо обычных массивов.
Чтобы выбрать тип коллекции для создания, передайте во время импорта схемы желаемый список типов коллекции в свойство ReferencedCollectionTypes объекта ImportOptions. Данные типы называются ссылочными типами коллекций.
При создании ссылки на них, универсальные типы должны быть либо полностью открытыми универсальными шаблонами, либо полностью закрытыми универсальными шаблонами.
Примечание |
---|
При использовании средства Svcutil.exe данная ссылка может быть выполнена с помощью переключателя командной строки /collectionType (сокращенная форма: /ct). Необходимо помнить, что нужно также указать сборку для типов ссылочных коллекций с помощью переключателя /reference (сокращенная форма: /r). Если тип является универсальным, после имени типа должен следовать обратный апостроф и число, указывающее количество универсальных параметров. Не следует путать символ обратного апострофа (`) со знаком одинарной кавычки (‘). Несколько коллекций ссылочного типа можно указывать несколько раз с помощью переключателя /collectionType. |
Например, чтобы все списки были импортированы как универсальные List.
svcutil.exe MyService.wsdl MyServiceSchema.xsd /r:C:\full_path_to_system_dll\System.dll /ct:System.Collections.Generic.List`1
При импорте какой-либо коллекции сканируется список коллекций ссылочного типа, и наиболее подходящая коллекция, если таковая найдена, используется как элемент данных (для ненастроенных коллекций) или как базовый тип для получения производных (для настроенных коллекций). Словари подходят только к словарям, а списки подходят только к спискам.
Например, при добавлении универсальных классов BindingList и 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 { }
Типы интерфейсов коллекции можно указать как коллекции ссылочного типа, но нельзя указать недействительные типы коллекций (такие как коллекции без метода Add или открытого конструктора).
Закрытый универсальный шаблон считается наиболее подходящим. (Неуниверсальные типы считаются эквивалентными закрытым универсальным Object). Например, если типы универсальных List, относящихся к DateTime, универсального BindingList (открытый универсальный шаблон) и 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> { }
При работе с коллекциями списков поддерживаются только указанные в следующей таблице случаи.
Ссылочный тип | Интерфейс, реализованный ссылочным типом | Пример | Тип обрабатывается как: |
---|---|---|---|
Неуниверсальный или закрытый универсальный (любое количество параметров) |
неуниверсальный |
или
где T= int |
Закрытый универсальный тип Object (например, |
Неуниверсальный или закрытый универсальный (любое количество параметров, которое не обязательно совпадает с типом коллекции) |
Закрытый универсальный тип |
или
|
Закрытый универсальный тип (например, |
Закрытый универсальный тип с любым количеством параметров |
Открытый универсальный тип с использованием одного из параметров типа |
где T=int, U=string, V=bool |
Закрытый универсальный тип (например, |
Открытый универсальный тип с одним параметром |
Открытый универсальный тип с использованием параметра типа |
|
Открытый универсальный тип (например, |
Если тип реализует несколько интерфейсов коллекции списка, применяются следующие ограничения.
Если тип несколько раз для разных типов реализует интерфейс IEnumerable (или наследованные от него интерфейсы), тип не рассматривается как действительная коллекция ссылочного типа и не учитываются. Это верно, даже если некоторые реализации недействительны или используют открытые универсальные шаблоны. Например, тип, реализующий универсальный интерфейс IEnumerable, относящийся к int, и универсальный интерфейс IEnumerable, относящийся к T, никогда не будет использован как коллекция ссылочного типа int или другого типа, независимо от того, имеется ли в типе метод Add, принимающий метод int, или Add, принимающий параметр типа T, или оба.
Если тип реализует универсальный интерфейс коллекции, а так же интерфейс IList, тип никогда не будет использован как коллекция ссылочного типа, кроме случая, когда интерфейс универсальной коллекции является закрытым универсальным шаблоном типа Object.
При работе с коллекциями-словарями поддерживаются только указанные в следующей таблице случаи.
Ссылочный тип | Интерфейс, реализованный ссылочным типом | Пример | Тип обрабатывается как |
---|---|---|---|
Неуниверсальный или закрытый универсальный (любое количество параметров) |
IDictionary |
или
|
Закрытый универсальный тип |
Закрытый универсальный тип (любое количество параметров) |
IDictionary, закрытый |
|
Закрытый универсальный тип (например, |
Закрытый универсальный тип (любое количество параметров) |
Универсальный IDictionary, ключ или значение закрыто, другой (ключ или значение) открыт и использует один из параметров типа. |
или
|
Закрытый универсальный тип (например, |
Закрытый универсальный тип (любое количество параметров) |
Универсальный IDictionary, ключ и значение открыты, и оба используют один из параметров типа |
|
Закрытый универсальный тип (например, |
Открытый универсальный тип (два параметра) |
Универсальный IDictionary, открытый, использует оба типа универсальных параметров в порядке их появления |
|
Открытый универсальный тип (например, |
Если тип реализует и интерфейс IDictionary, и универсальный интерфейс IDictionary, рассматривается только универсальный IDictionary.
Ссылка на частично универсальные типы не поддерживается.
Дубликаты не разрешены. Например, нельзя добавить одновременно универсальный класс List типа Integer и универсальную коллекцию Integer в ReferencedCollectionTypes, поскольку в таком случае будет невозможно определить, какой из них использовать при обнаружении списка целых чисел в схеме. Дубликаты обнаруживаются, только если в схеме есть тип, указывающий на проблему дубликатов. Например, если импортируемая схема не содержит список целых чисел, допускается иметь в ReferencedCollectionTypes как универсальный List типа Integer, так и универсальную коллекцию Integer, но ни тот, ни другой не будут действовать.
Расширенные правила коллекции
Сериализация коллекций
Далее приводится список правил сериализации коллекций.
Разрешено комбинирование типов коллекции (наличие коллекций коллекций). Неровные массивы обрабатываются как коллекции коллекций. Многомерные массивы не поддерживаются.
Массивы байтов и массивы XmlNode являются специальными типами массивов, которые обрабатываются как базисные элементы, а не коллекции. В результате сериализации массива байтов получается одиночный элемент XML, который содержит фрагмент данных, закодированных в Base64, вместо отдельного элемента для каждого байта. Дополнительные сведения том, как обрабатывается массив XmlNode, см. в Типы XML и ADO.NET в контрактах данных. Конечно, такие специальные типы могут сами по себе участвовать в коллекциях: результатом массива массивов байтов является несколько элементов XML, каждый из которых содержит фрагмент данных, закодированных в Base64.
Если к типу коллекции применяется атрибут DataContractAttribute, тип обрабатывается как тип с обычным контрактом данных, а не как коллекция.
Если тип коллекции реализует интерфейс IXmlSerializable, применяются следующие правила для типа
myType:IList<string>, IXmlSerializable
.Если объявленный тип —
IList<string>
, тип сериализуется как список.Если объявленный тип —
myType
, он сериализуется как IXmlSerializable.Если объявленный тип — IXmlSerializable, он сериализуется как IXmlSerializable, но только при условии добавления
myType
в список известных типов.
Коллекции сериализуются и десериализуются с помощью методов, показанных в следующей таблице.
Реализованные в коллекции элементы | Методы, вызываемые при сериализации | Методы, вызываемые при десериализации |
---|---|---|
Универсальный тип IDictionary |
get_Keys, get_Values |
Универсальное добавление |
IDictionary |
get_Keys, get_Values |
Add |
Универсальный тип IList |
Универсальный тип IList |
Универсальное добавление |
Универсальный тип ICollection |
Enumerator |
Универсальное добавление |
IList |
Индексатор IList |
Add |
Универсальный тип IEnumerable |
GetEnumerator |
Нестатический метод с названием Add, принимающий один параметр соответствующего типа (тип универсального параметра или один из его базовых типов). Такой метод должен быть предусмотрен для обработки коллекции сериализатором во время сериализации и десериализации. |
IEnumerable (и, следовательно, унаследованный от него ICollection) |
GetEnumerator |
Нестатический метод с названием Add, принимающий один параметр типа Object. Такой метод должен быть предусмотрен для обработки коллекции сериализатором во время сериализации и десериализации. |
В предыдущей таблице приведен список интерфейсов коллекции, упорядоченный по убыванию приоритета. Это означает, что, например, если тип реализует интерфейс IList и универсальный интерфейс IEnumerable, коллекция сериализуется и десериализуется согласно правилам интерфейса IList:
При десериализации все коллекции десериализуются путем создания, в первую очередь, экземпляра типа путем вызова конструктора по умолчанию, присутствие которого обеспечивает обработку сериализатором типа коллекции как коллекции во время сериализации и десериализации.
Если один и тот же универсальный интерфейс коллекции реализуется несколько раз (например, если тип одновременно реализует как универсальный интерфейс ICollection типа Integer, так и универсальный интерфейс ICollection типа String), и при этом не обнаружен интерфейс с более высоким приоритетом, коллекция не обрабатывается как действительная.
Типы коллекций могут иметь применяемый к ним атрибут SerializableAttribute и могут реализовать интерфейс ISerializable. Оба данных типа не учитываются. Однако если тип не полностью соответствует требованиям к типу коллекции (например, отсутствует метод Add), тип не рассматривается как тип коллекции, и поэтому для определения того, можно ли сериализовать данный тип, используются атрибут SerializableAttribute и интерфейс ISerializable.
При применении атрибута CollectionDataContractAttribute к коллекции для ее настройки удаляется предшествующий резервный механизм SerializableAttribute. Вместо этого, если настроенная коллекция не соответствует требованиям к типу коллекции, выдается исключение InvalidDataContractException. Строка исключения часто содержит информацию, в которой объясняется, почему данный тип не считается действительной коллекцией (отсутствует метод Add, отсутствует конструктор по умолчанию, и т. п.), поэтому при отладке может быть полезным применение атрибута CollectionDataContractAttribute.
Именование коллекций
Далее приводится список правил именования коллекций.
Пространство имен по умолчанию для всех контрактов данных коллекций-словарей, а также для контрактов данных коллекций списков, содержащих типы-примитивы, см. https://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, как описано в следующем разделе.
Правила присвоения имени универсальному типу, в соответствии с описанием в Имена контрактов данных, полностью применяется к типам коллекций, что означает, что для обозначения параметров универсального типа можно использовать фигурные скобки. Однако числа, указанные в скобках, относятся к универсальным параметрам, а не к типам, содержащимся в коллекции.
Настройка коллекции
Следующие варианты использования атрибута CollectionDataContractAttribute запрещены и приводят к вызову исключения InvalidDataContractException:
Применение атрибута DataContractAttribute к типу, к которому был применен атрибут CollectionDataContractAttribute, или к одному из наследованных от него типов.
Применение атрибута CollectionDataContractAttribute к типу, реализующему интерфейс IXmlSerializable.
Применение атрибута CollectionDataContractAttribute к типу, не являющемуся коллекцией.
Попытка задать KeyName или ValueName атрибуту CollectionDataContractAttribute, применяемому к типу, не являющемуся словарем.
Правила полиморфизма
Как было указано выше, настройка коллекций с помощью атрибута CollectionDataContractAttribute может мешать взаимозаменяемости коллекций. Два типа настроенных коллекций могут считаться эквивалентными только при условии совпадения их имен, пространств имен, имен элементов, а также имен ключей и значений (если это коллекции-словари).
Благодаря возможности настроек, можно случайно использовать один контракт данных там, где ожидается другой. Этого следует избегать. См. указанные ниже типы.
[DataContract] public class Student {
[DataMember] public string name;
[DataMember] public IList<int> testMarks;
}
public class Marks1 : List<int> {}
[CollectionDataContract(ItemName="mark")]
public class Marks2 : List<int> {}
В таком случае экземпляру Marks1
может быть присвоен testMarks
. Однако Marks2
не должен использоваться, поскольку его контракт данных рассматривается как эквивалентный контракту данных IList<int>
. Именем контракта данных является «Marks2», а не «ArrayOfint», а именем повторяющегося элемента является «<mark>», а не «<int>».
Правила, приведенные в следующей таблице, применяются к полиморфному назначению коллекций.
Объявленный тип | Назначение ненастроенной коллекции | Назначение настроенной коллекции |
---|---|---|
Объект |
Имя контракта сериализовано. |
Имя контракта сериализовано. Используется настройка. |
Интерфейс коллекции |
Имя контракта не сериализовано. |
Имя контракта не сериализовано. Настройка не используется.* |
Ненастроенная коллекция |
Имя контракта не сериализовано. |
Имя контракта сериализовано. Используется настройка.** |
Настроенная коллекция |
Имя контракта сериализовано. Настройка не используется.** |
Имя контракта сериализовано. Используется настройка назначенного типа.** |
*С классом NetDataContractSerializer в данном случае используется настройка. В данном случае класс NetDataContractSerializer также сериализует текущее имя типа, т. е. десериализация выполняется так, как ожидалось.
**В данных случаях получаются экземпляры с недействительной схемой. Этого следует избегать.
В случаях, когда имя контракта сериализовано, назначенный тип коллекции должен находиться в списке известных типов. Также верно и противоположное: в случаях, когда имя не сериализовалось, добавление типа в список известных типов не требуется.
Массив унаследованного типа может быть соотнесен с массивом базового типа. В таком случае имя контракта наследованного типа сериализуется для каждого повторяющегося элемента. Например, если тип Book унаследован от типа LibraryItem, массив Book можно соотнести с массивом LibraryItem. Это не распространяется на другие типы коллекций. Например, нельзя соотнести Generic List of Book с Generic List of LibraryItem. Можно, однако, соотнести Generic List of LibraryItem, содержащий экземпляры Book. В обоих случаях, с массивом и без массива, Book должен быть в списке известных типов.
Сохранение коллекций и ссылок на объект
Когда сериализатор работает в режиме сохранения ссылок на объект, сохранение ссылок на объект также распространяется на коллекции. В частности, идентичность объекта сохраняется и во всей коллекции, и в отдельных элементах, содержащихся в коллекциях. В словарях удостоверение объекта сохраняется как в объектах пары ключ-значение, так и в отдельных объектах ключа и значения.