Collection Types in Data Contracts

A collection is a list of items of a certain type. In the .NET Framework, such lists can be represented using arrays or a variety of other types (Generic List, Generic BindingList<T>, StringCollection, or ArrayList). For example, a collection may hold a list of Addresses for a given Customer. These collections are called list collections, regardless of their actual type.

A special form of collection exists that represents an association between one item (the "key") and another (the "value"). In the .NET Framework, these are represented by types such as Hashtable and the generic dictionary. For example, an association collection may map a city ("key") to its population ("value"). These collections are called dictionary collections, regardless of their actual type.

Collections receive special treatment in the data contract model.

Types that implement the IEnumerable interface, including arrays and generic collections, are recognized as collections. Of those, types that implement the IDictionary or Generic IDictionary<TKey,TValue> interfaces are dictionary collections; all others are list collections.

Additional requirements on collection types, such as having a method called Add and a parameterless constructor, are discussed in detail in the following sections. This ensures that collection types can be both serialized and deserialized. This means that some collections are not directly supported, such as the Generic ReadOnlyCollection<T> (because it has no parameterless constructor). However, for information about circumventing these restrictions, see the section "Using Collection Interface Types and Read-Only Collections" later in this topic.

The types contained in collections must be data contract types, or be otherwise serializable. For more information, see Types Supported by the Data Contract Serializer.

For more information about what is and what is not considered a valid collection, as well as about how collections are serialized, see the information about serializing collections in the "Advanced Collection Rules" section of this topic.

Interchangeable Collections

All list collections of the same type are considered to have the same data contract (unless they are customized using the CollectionDataContractAttribute attribute, as discussed later in this topic). Thus, for example, the following data contracts are equivalent.

[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

Both data contracts result in XML similar to the following code.

<PurchaseOrder>
    <customerName>...</customerName>
    <items>
        <Item>...</Item>
        <Item>...</Item>
        <Item>...</Item>
        ...
    </items>
    <comments>
        <string>...</string>
        <string>...</string>
        <string>...</string>
        ...
    </comments>
</PurchaseOrder>

Collection interchangeability allows you to use, for example, a collection type optimized for performance on the server and a collection type designed to be bound to user interface components on the client.

Similar to list collections, all dictionary collections that have the same key and value types are considered to have the same data contract (unless customized by the CollectionDataContractAttribute attribute).

Only the data contract type matters as far as collection equivalence is concerned, not .NET types. That is, a collection of Type1 is considered equivalent to a collection of Type2 if Type1 and Type2 have equivalent data contracts.

Non-generic collections are considered to have the same data contract as generic collections of type Object. (For example, the data contracts for ArrayList and Generic List<T> of Object are the same.)

Using Collection Interface Types and Read-Only Collections

Collection interface types (IEnumerable, IDictionary, generic IDictionary<TKey,TValue>, or interfaces derived from these interfaces) are also considered as having collection data contracts, equivalent to collection data contracts for actual collection types. Thus, it is possible to declare the type being serialized as a collection interface type and the results are the same as if an actual collection type had been used. For example, the following data contracts are equivalent.

[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

During serialization, when the declared type is an interface, the actual instance type used can be any type that implements that interface. Restrictions discussed previously (having a parameterless constructor and an Add method) do not apply. For example, you can set addresses in Customer2 to an instance of Generic ReadOnlyCollection<T> of Address, even though you cannot directly declare a data member of type Generic ReadOnlyCollection<T>.

During deserialization, when the declared type is an interface, the serialization engine chooses a type that implements the declared interface, and the type is instantiated. The known types mechanism (described in Data Contract Known Types) has no effect here; the choice of type is built into WCF.

Customizing Collection Types

You can customize collection types by using the CollectionDataContractAttribute attribute, which has several uses.

Note that customizing collection types compromises collection interchangeability, so it is generally recommended to avoid applying this attribute whenever possible. For more information about this issue, see the "Advanced Collection Rules" section later in this topic.

Collection Data Contract Naming

The rules for naming collection types are similar to those for naming regular data contract types, as described in Data Contract Names, although some important differences exist:

  • The CollectionDataContractAttribute attribute is used to customize the name, instead of the DataContractAttribute attribute. The CollectionDataContractAttribute attribute also has Name and Namespace properties.

  • When the CollectionDataContractAttribute attribute is not applied, the default name and namespace for collection types depend on the names and namespaces of types contained within the collection. They are not affected by the name and namespace of the collection type itself. For an example, see the following types.

    public CustomerList1 : Collection<string> {}
    public StringList1 : Collection<string> {}
    

Both types’ data contract name is "ArrayOfstring" and not "CustomerList1" or "StringList1". This means that serializing any one of these types at the root level yields XML similar to the following code.

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

This naming rule was chosen to ensure that any non-customized type that represents a list of strings has the same data contract and XML representation. This makes collection interchangeability possible. In this example, CustomerList1 and StringList1 are completely interchangeable.

However, when the CollectionDataContractAttribute attribute is applied, the collection becomes a customized collection data contract, even if no properties are set on the attribute. The name and namespace of the collection data contract then depend on the collection type itself. For an example, see the following type.

[CollectionDataContract]
public class CustomerList2 : Collection<string> {}
<CollectionDataContract()>
Public Class CustomerList2
    Inherits Collection(Of String)
End Class

When serialized, the resulting XML is similar to the following.

<CustomerList2>
    <string>...</string>
    <string>...</string>
    <string>...</string>
    ...
</CustomerList2>

Notice that this is no longer equivalent to the XML representation of the non-customized types.

  • You can use the Name and Namespace properties to further customize the naming. See the following class.

    [CollectionDataContract(Name="cust_list")]
    public class CustomerList3 : Collection<string> {}
    
    <CollectionDataContract(Name:="cust_list")>
    Public Class CustomerList3
        Inherits Collection(Of String)
    End Class
    

The resulting XML is similar to the following.

<cust_list>
    <string>...</string>
    <string>...</string>
    <string>...</string>
    ...
</cust_list>

For more information, see the "Advanced Collection Rules" section later in this topic.

Customizing the Repeating Element Name in List Collections

List collections contain repeating entries. Normally, each repeating entry is represented as an element named according to the data contract name of the type contained in the collection.

In the CustomerList examples, the collections contained strings. The data contract name for the string primitive type is "string", so the repeating element was "<string>".

However, using the ItemName property on the CollectionDataContractAttribute attribute, this repeating element name can be customized. For an example, see the following type.

[CollectionDataContract(ItemName="customer")]
public class CustomerList4 : Collection<string>  {}
<CollectionDataContract(ItemName:="customer")>
Public Class CustomerList4
    Inherits Collection(Of String)
End Class

The resulting XML is similar to the following.

<CustomerList4>
    <customer>...</customer>
    <customer>...</customer>
    <customer>...</customer>
    ...
</CustomerList4>

The namespace of the repeating element is always the same as the namespace of the collection data contract, which can be customized using the Namespace property, as described previously.

Customizing Dictionary Collections

Dictionary collections are essentially lists of entries, where each entry has a key followed by a value. Just as with regular lists, you can change the element name that corresponds to the repeating element by using the ItemName property.

Additionally, you can change the element names that represent the key and the value by using the KeyName and ValueName properties. The namespaces for these elements are the same as the namespace of the collection data contract.

For an example, see the following type.

[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

When serialized, the resulting XML is similar to the following.

<CountriesOrRegionsWithCapitals>
    <entry>
        <countryorregion>USA</countryorregion>
        <capital>Washington</capital>
    </entry>
    <entry>
        <countryorregion>France</countryorregion>
        <capital>Paris</capital>
    </entry>
    ...
</CountriesOrRegionsWithCapitals>

For more information about dictionary collections, see the "Advanced Collection Rules" section later in this topic.

Collections and Known Types

You do not need to add collection types to known types when used polymorphically in place of other collections or collection interfaces. For example, if you declare a data member of type IEnumerable and use it to send an instance of ArrayList, you do not need to add ArrayList to known types.

When you use collections polymorphically in place of non-collection types, they must be added to known types. For example, if you declare a data member of type Object and use it to send an instance of ArrayList, add ArrayList to known types.

This does not allow you to serialize any equivalent collection polymorphically. For example, when you add ArrayList to the list of known types in the preceding example, this does not let you assign the Array of Object class, even though it has an equivalent data contract. This is no different from regular known types behavior on serialization for non-collection types, but it is especially important to understand in the case of collections because it is very common for collections to be equivalent.

During serialization, only one type can be known in any given scope for a given data contract, and equivalent collections all have the same data contracts. This means that, in the previous example, you cannot add both ArrayList and Array of Object to known types at the same scope. Again, this is equivalent to known types behavior for non-collection types, but it is especially important to understand for collections.

Known types may also be required for contents of collections. For example, if an ArrayList actually contains instances of Type1 and Type2, both of these types should be added to known types.

The following example shows a properly constructed object graph using collections and known types. The example is somewhat contrived, because in an actual application you would normally not define the following data members as Object, and thus do not have any known type/polymorphism issues.

[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

On deserialization, if the declared type is a collection type, the declared type is instantiated regardless of the type that was actually sent. If the declared type is a collection interface, the deserializer picks a type to be instantiated with no regard to known types.

Also on deserialization, if the declared type is not a collection type but a collection type is being sent, a matching collection type is picked out of the known types list. It is possible to add collection interface types to the list of known types on deserialization. In this case, the deserialization engine again picks a type to be instantiated.

Collections and the NetDataContractSerializer Class

When the NetDataContractSerializer class is in use, non-customized collection types (without the CollectionDataContractAttribute attribute) that are not arrays lose their special meaning.

Non-customized collection types marked with the SerializableAttribute attribute can still be serialized by the NetDataContractSerializer class according to the SerializableAttribute attribute or the ISerializable interface rules.

Customized collection types, collection interfaces, and arrays are still treated as collections, even when the NetDataContractSerializer class is in use.

Collections and Schema

All equivalent collections have the same representation in XML Schema definition language (XSD) schema. Because of this, you normally do not get the same collection type in the generated client code as the one on the server. For example, the server may use a data contract with a Generic List<T> of Integer data member, but in the generated client code the same data member may become an array of integers.

Dictionary collections are marked with a WCF-specific schema annotation that indicate that they are dictionaries; otherwise, they are indistinguishable from simple lists that contain entries with a key and a value. For an exact description of how collections are represented in data contract schema, see Data Contract Schema Reference.

By default, types are not generated for non-customized collections in imported code. Data members of list collection types are imported as arrays, and data members of dictionary collection types are imported as Generic Dictionary.

However, for customized collections, separate types are generated, marked with the CollectionDataContractAttribute attribute. (A customized collection type in the schema is one that does not use the default namespace, name, repeating element name, or key/value element names.) These types are empty types that derive from Generic List<T> for list types and Generic Dictionary for dictionary types.

For example, you may have the following types on the server.

[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

When the schema is exported and imported back again, the generated client code is similar to the following (fields are shown instead of properties for ease of reading).

[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

You may want to use different types in generated code than the default ones. For example, you may want to use Generic BindingList<T> instead of regular arrays for your data members to make it easier to bind them to user interface components.

To choose collection types to generate, pass a list of collection types you want to use into the ReferencedCollectionTypes property of the ImportOptions object when importing schema. These types are called referenced collection types.

When generic types are being referenced, they must either be fully-open generics or fully-closed generics.

Note

When using the Svcutil.exe tool, this reference can be accomplished by using the /collectionType command-line switch (short form: /ct). Keep in mind that you must also specify the assembly for the referenced collection types using the /reference switch (short form: /r). If the type is generic, it must be followed by a back quote and the number of generic parameters. The back quote (`) is not to be confused with the single quote (‘) character. You can specify multiple referenced collection types by using the /collectionType switch more than once.

For example, to cause all lists to be imported as Generic List<T>.

svcutil.exe MyService.wsdl MyServiceSchema.xsd /r:C:\full_path_to_system_dll\System.dll /ct:System.Collections.Generic.List`1

When importing any collection, this list of referenced collection types is scanned, and the best-matching collection is used if one is found, either as a data member type (for non-customized collections) or as a base type to derive from (for customized collections). Dictionaries are only matched against dictionaries, while lists are matched against lists.

For example, if you add the Generic BindingList<T> and Hashtable to the list of referenced types, the generated client code for the preceding example is similar to the following.

[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

You can specify collection interface types as part of your referenced collection types, but you cannot specify invalid collection types (such as ones with no Add method or public constructor).

A closed generic is considered to be the best match. (Non-generic types are considered equivalent to closed generics of Object). For example, if the types Generic List<T> of DateTime, Generic BindingList<T> (open generic), and ArrayList are the referenced collection types, the following is generated.

[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

For list collections, only the cases in the following table are supported.

Referenced type Interface implemented by referenced type Example Type treated as:
Non-generic or closed generic (any number of parameters) Non-generic MyType : IList

or

MyType<T> : IList

where T= int
Closed generic of Object (for example, IList<object>)
Non-generic or closed generic (any number of parameters that do not necessarily match the collection type) Closed generic MyType : IList<string>

or

MyType<T> : IList<string> where T=int
Closed generic (for example, IList<string>)
Closed generic with any number of parameters Open generic using any one of the type’s parameters MyType<T,U,V> : IList<U>

where T=int, U=string, V=bool
Closed generic (for example, IList<string>)
Open generic with one parameter Open generic using the type’s parameter MyType<T> : IList<T>, T is open Open generic (for example, IList<T>)

If a type implements more than one list collection interface, the following restrictions apply:

  • If the type implements Generic IEnumerable<T> (or its derived interfaces) multiple times for different types, the type is not considered a valid referenced collection type and is ignored. This is true even if some implementations are invalid or use open generics. For example, a type that implements Generic IEnumerable<T> of int and Generic IEnumerable<T> of T would never be used as a referenced collection of int or any other type, regardless of whether the type has an Add method accepting int or an Add method accepting a parameter of type T, or both.

  • If the type implements a generic collection interface as well as IList, the type is never used as a referenced collection type unless the generic collection interface is a closed generic of type Object.

For dictionary collections, only the cases in the following table are supported.

Referenced type Interface implemented by referenced type Example Type treated as
Non-generic or closed generic (any number of parameters) IDictionary MyType : IDictionary

or

MyType<T> : IDictionary where T=int
Closed generic IDictionary<object,object>
Closed generic (any number of parameters) IDictionary<TKey,TValue>, closed MyType<T> : IDictionary<string, bool> where T=int Closed generic (for example, IDictionary<string,bool>)
Closed generic (any number of parameters) Generic IDictionary<TKey,TValue>, one of either key or value is closed, the other is open and uses one of type’s parameters MyType<T,U,V> : IDictionary<string,V> where T=int, U=float,V=bool

or

MyType<Z> : IDictionary<Z,bool> where Z=string
Closed generic (For example, IDictionary<string,bool>)
Closed generic (any number of parameters) Generic IDictionary<TKey,TValue>, both key and value are open and each uses one of the type’s parameters MyType<T,U,V> : IDictionary<V,U> where T=int, U=bool, V=string Closed generic (for example, IDictionary<string,bool>)
Open generic (two parameters) Generic IDictionary<TKey,TValue>, open, uses both of the type’s generic parameters in the order they appear MyType<K,V> : IDictionary<K,V>, K and V both open Open generic (for example, IDictionary<K,V>)

If the type implements both IDictionary and Generic IDictionary<TKey,TValue>, only Generic IDictionary<TKey,TValue> is considered.

Referencing partial generic types is not supported.

Duplicates are not allowed, for example, you cannot add both the Generic List<T> of Integer and the Generic Collection of Integer to ReferencedCollectionTypes, because this makes it impossible to determine which one to use when a list of integers is found in the schema. Duplicates are detected only if there is a type in the schema that exposes the duplicates problem. For example, if the schema being imported does not contain lists of integers, it is allowed to have both the Generic List<T> of Integer and the Generic Collection of Integer in the ReferencedCollectionTypes, but neither has any effect.

Advanced Collection Rules

Serializing Collections

The following is a list of collection rules for serialization:

  • Combining collection types (having collections of collections) is allowed. Jagged arrays are treated as collections of collections. Multidimensional arrays are not supported.

  • Arrays of byte and arrays of XmlNode are special array types that are treated as primitives, not collections. Serializing an array of byte results in a single XML element that contains a chunk of Base64-encoded data, instead of a separate element for each byte. For more information about how an array of XmlNode is treated, see XML and ADO.NET Types in Data Contracts. Of course, these special types can themselves participate in collections: an array of array of byte results in multiple XML elements, with each containing a chunk of Base64-encoded data.

  • If the DataContractAttribute attribute is applied to a collection type, the type is treated as a regular data contract type, not as a collection.

  • If a collection type implements the IXmlSerializable interface, the following rules apply, given a type myType:IList<string>, IXmlSerializable:

    • If the declared type is IList<string>, the type is serialized as a list.

    • If the declared type is myType, it is serialized as IXmlSerializable.

    • If the declared type is IXmlSerializable, it is serialized as IXmlSerializable, but only if you add myType to the list of known types.

  • Collections are serialized and deserialized using the methods shown in the following table.

Collection type implements Method(s) called on serialization Method(s) called on deserialization
Generic IDictionary<TKey,TValue> get_Keys, get_Values Generic Add
IDictionary get_Keys, get_Values Add
Generic IList<T> Generic IList<T> indexer Generic Add
Generic ICollection<T> Enumerator Generic Add
IList IList Indexer Add
Generic IEnumerable<T> GetEnumerator A non-static method called Add that takes one parameter of the appropriate type (the type of the generic parameter or one of its base types). Such a method must exist for the serializer to treat a collection type as a collection during both serialization and deserialization.
IEnumerable (and thus ICollection, which derives from it) GetEnumerator A non-static method called Add that takes one parameter of type Object. Such a method must exist for the serializer to treat a collection type as a collection during both serialization and deserialization.

The preceding table lists collection interfaces in descending order of precedence. This means, for example, that if a type implements both IList and Generic IEnumerable<T>, the collection is serialized and deserialized according to the IList rules:

  • At deserialization, all collections are deserialized by first creating an instance of the type by calling the parameterless constructor, which must be present for the serializer to treat a collection type as a collection during both serialization and deserialization.

  • If the same generic collection interface is implemented more than once (for example, if a type implements both Generic ICollection<T> of Integer and Generic ICollection<T> of String) and no higher-precedence interface is found, the collection is not treated as a valid collection.

  • Collection types can have the SerializableAttribute attribute applied to them and can implement the ISerializable interface. Both of these are ignored. However, if the type does not fully meet collection type requirements (for example, the Add method is missing), the type is not considered a collection type, and thus the SerializableAttribute attribute and the ISerializable interface are used to determine whether the type can be serialized.

  • Applying the CollectionDataContractAttribute attribute to a collection to customize it removes the SerializableAttribute preceding fallback mechanism. Instead, if a customized collection does not meet collection type requirements, an InvalidDataContractException exception is thrown. The exception string often contains information that explains why a given type is not considered a valid collection (no Add method, no parameterless constructor, and so on), so it is often useful to apply the CollectionDataContractAttribute attribute for debugging purposes.

Collection Naming

The following is a list of collection naming rules:

  • The default namespace for all dictionary collection data contracts, as well as for list collection data contracts that contain primitive types, is http://schemas.microsoft.com/2003/10/Serialization/Arrays unless overridden using Namespace. Types that map to built-in XSD types, as well as char, Timespan, and Guid types, are considered primitives for this purpose.

  • The default namespace for collection types that contain non-primitive types, unless it is overridden using Namespace, is the same as the data contract namespace of the type contained in the collection.

  • The default name for list collection data contracts, unless overridden using Name, is the string "ArrayOf" combined with the data contract name of the type contained in the collection. For example, the data contract name for a Generic List of Integers is "ArrayOfint". Keep in mind that the data contract name of Object is "anyType", so the data contract name of non-generic lists like ArrayList is "ArrayOfanyType".

The default name for dictionary collection data contracts, unless overridden using Name, is the string "ArrayOfKeyValueOf" combined with the data contract name of the key type followed by the data contract name of the value type. For example, the data contract name for a Generic Dictionary of String and Integer is "ArrayOfKeyValueOfstringint". Additionally, if either the key or the value types are not primitive types, a namespace hash of the data contract namespaces of the key and value types is appended to the name. For more information about namespace hashes, see Data Contract Names.

Each dictionary collection data contract has a companion data contract that represents one entry in the dictionary. Its name is the same as for the dictionary data contract, except for the "ArrayOf" prefix, and its namespace is the same as for the dictionary data contract. For example, for the "ArrayOfKeyValueOfstringint" dictionary data contract, the "KeyValueofstringint" data contract represents one entry in the dictionary. You can customize the name of this data contract by using the ItemName property, as described in the next section.

Generic type naming rules, as described in Data Contract Names, fully apply to collection types; that is, you can use curly braces within Name to indicate generic type parameters. However, numbers within the braces refer to generic parameters and not types contained within the collection.

Collection Customization

The following uses of the CollectionDataContractAttribute attribute are forbidden and result in an InvalidDataContractException exception:

Polymorphism Rules

As previously mentioned, customizing collections by using the CollectionDataContractAttribute attribute may interfere with collection interchangeability. Two customized collection types can only be considered equivalent if their name, namespace, item name, as well as key and value names (if these are dictionary collections) match.

Due to customizations, it is possible to inadvertently use one collection data contract where another is expected. This should be avoided. See the following types.

[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

In this case, an instance of Marks1 can be assigned to testMarks. However, Marks2 should not be used because its data contract is not considered equivalent to the IList<int> data contract. The data contract name is "Marks2" and not "ArrayOfint", and the repeating element name is "<mark>" and not "<int>".

The rules in the following table apply to polymorphic assignment of collections.

Declared type Assigning a non-customized collection Assigning a customized collection
Object Contract name is serialized. Contract name is serialized.

Customization is used.
Collection interface Contract name is not serialized. Contract name is not serialized.

Customization is not used.*
Non-customized collection Contract name is not serialized. Contract name is serialized.

Customization is used.**
Customized collection Contract name is serialized. Customization is not used.** Contract name is serialized.

Customization of the assigned type is used.**

*With the NetDataContractSerializer class, customization is used in this case. The NetDataContractSerializer class also serializes the actual type name in this case, so deserialization works as expected.

**These cases result in schema-invalid instances and thus should be avoided.

In the cases where the contract name is serialized, the assigned collection type should be in the known types list. The opposite is also true: in the cases where the name is not serialized, adding the type to the known types list is not required.

An array of a derived type can be assigned to an array of a base type. In this case, the contract name for the derived type is serialized for each repeating element. For example, if a type Book derives from the type LibraryItem, you can assign an array of Book to an array of LibraryItem. This does not apply to other collection types. For example, you cannot assign a Generic List of Book to a Generic List of LibraryItem. You can, however, assign a Generic List of LibraryItem that contains Book instances. In both the array and the non-array case, Book should be in the known types list.

Collections and Object Reference Preservation

When a serializer functions in a mode where it preserves object references, object reference preservation also applies to collections. Specifically, object identity is preserved for both entire collections and individual items contained in collections. For dictionaries, object identity is preserved both for the key/value pair objects and the individual key and value objects.

See also