Tipos de coleção em contratos de dados

Uma coleção é uma lista de itens de um determinado tipo. No .NET Framework, essas listas podem ser representadas usando matrizes ou outros tipos (Lista genérica, BindingList<T>, StringCollection ou ArrayListgenérico). Por exemplo, uma coleção pode conter uma lista de endereços para um determinado cliente. Essas coleções são chamadas coleções de lista, independente do seu tipo real.

Existe uma forma especial de coleção que representa uma associação entre um item ("chave") e outro ("valor"). No .NET Framework, eles são representados por tipos como Hashtable e o dicionário genérico. Por exemplo, uma coleção de associações pode mapear uma cidade ("chave") para sua população ("valor"). Essas coleções são chamadas coleções de dicionário, independente do seu tipo real.

As coleções recebem tratamento especial no modelo de contrato de dados.

Os tipos que implementam a interface IEnumerable, incluindo matrizes e coleções genéricas, são reconhecidos como coleções. Desses, os tipos que implementam as interfaces IDictionary ou IDictionary<TKey,TValue> genéricos são coleções de dicionários. Todas as outras são coleções de listas.

Requisitos adicionais sobre tipos de coleção, como ter uma chamada de método Add e um construtor sem parâmetros, são discutidos detalhadamente nas seções a seguir. Isso garante que os tipos de coleção possam ser serializados e desserializados. Isso significa que algumas coleções não têm suporte direto, como o ReadOnlyCollection<T> genérico (porque não tem construtor sem parâmetros). No entanto, para obter informações sobre como ignorar essas restrições, consulte a seção "Usando tipos de interface de coleção e coleções somente leitura" abaixo neste tópico.

Os tipos contidos nas coleções devem ser tipos de contrato de dados ou serializáveis de outra forma. Para saber mais, consulte Tipos compatíveis com o Serializador de Contrato de Dados.

Para obter mais informações sobre o que é e o que não é considerado uma coleção válida e sobre como as coleções são serializadas, consulte as informações sobre serialização de coleções na seção "Regras avançadas de coleção" deste tópico.

Coleções intercambiáveis

Todas as coleções de listas do mesmo tipo são consideradas com o mesmo contrato de dados (a menos que sejam personalizadas com o atributo CollectionDataContractAttribute, conforme discutido abaixo neste tópico). Assim, por exemplo, os contratos de dados a seguir são equivalentes.

[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

Ambos os contratos de dados resultam em XML semelhante ao código a seguir.

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

A intercambiabilidade de coleção permite que você use, por exemplo, um tipo de coleção otimizado para desempenho no servidor e um tipo de coleção criado para ser associado a componentes de interface do usuário no cliente.

Semelhante às coleções de listas, todas as coleções de dicionários que têm os mesmos tipos de chave e valor são consideradas com o mesmo contrato de dados (a menos que personalizados pelo atributo CollectionDataContractAttribute).

Somente o tipo de contrato de dados é importante no que diz respeito à equivalência da coleção, não aos tipos .NET. Ou seja, uma coleção de Type1 é considerada equivalente a uma coleção de Type2, se Type1 e Type2 tiverem contratos de dados equivalentes.

As coleções não genéricas são consideradas com o mesmo contrato de dados que as coleções genéricas do tipo Object. (Por exemplo, os contratos de dados para ArrayList e genéricos List<T>Object são iguais.)

Usando tipos de interface de coleção e coleções somente leitura

Os tipos de interface de coleção (IEnumerable, IDictionary, IDictionary<TKey,TValue> genéricos ou interfaces derivadas dessas interfaces) também são considerados como tendo contratos de dados de coleção equivalentes a contratos de dados de coleção para tipos de coleção reais. Assim, é possível declarar o tipo que está sendo serializado como um tipo de interface de coleção e os resultados são iguais se um tipo de coleção real tivesse sido usado. Por exemplo, os contratos de dados a seguir são equivalentes.

[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

Durante a serialização, quando o tipo declarado é uma interface, o tipo de instância real usado pode ser qualquer tipo que implemente essa interface. As restrições discutidas anteriormente (com um construtor sem parâmetros e um método Add) não se aplicam. Por exemplo, é possível definir endereços no Customer2 como uma instância de ReadOnlyCollection<T> genérico de endereço, embora não seja possível declarar diretamente um membro de dados do tipo ReadOnlyCollection<T> genérico.

Durante a desserialização, quando o tipo declarado é uma interface, o mecanismo de serialização escolhe um tipo que implementa a interface declarada e o tipo cria uma instância. O mecanismo de tipos conhecidos (descrito em Tipos Conhecidos do Contrato de Dados) não tem efeito aqui. A escolha do tipo é incorporada ao WCF.

Personalizando tipos de coleção

É possível personalizar tipos de coleção usando o atributo CollectionDataContractAttribute, que tem vários usos.

Observe que personalizar tipos de coleção compromete a intercambiabilidade da coleção, portanto, geralmente é recomendável evitar a aplicação desse atributo sempre que possível. Para obter mais informações sobre esse problema, consulte a seção "Regras de coleção avançadas" abaixo neste tópico.

Nomenclatura do contrato de dados de coleção

As regras para tipos de coleção de nomenclatura são semelhantes àquelas para nomear tipos regulares de contrato de dados, conforme descrito em Nomes de Contrato de Dados, embora existam algumas diferenças importantes:

  • O atributo CollectionDataContractAttribute é usado para personalizar o nome, em vez do atributo DataContractAttribute. O atributo CollectionDataContractAttribute também tem propriedades Name e Namespace.

  • Quando o atributo CollectionDataContractAttribute não é aplicado, o nome padrão e o namespace para tipos de coleção dependem dos nomes e namespaces dos tipos contidos na coleção. Eles não são afetados pelo nome e pelo namespace do próprio tipo de coleção. Por exemplo, confira os tipos a seguir.

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

O nome do contrato de dados dos dois tipos é "ArrayOfstring" e não "CustomerList1" ou "StringList1". Isso significa que serializar qualquer um desses tipos no nível raiz gera XML semelhante ao código a seguir.

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

Essa regra de nomenclatura foi escolhida para garantir que qualquer tipo não personalizado que represente uma lista de cadeias de caracteres tenha o mesmo contrato de dados e representação XML. Assim, é possível a intercambiabilidade de coleção. Neste exemplo, CustomerList1 e StringList1 são completamente intercambiáveis.

No entanto, quando o atributo CollectionDataContractAttribute é aplicado, a coleção gera um contrato de dados de coleção personalizado, mesmo que nenhuma propriedade seja definida no atributo. O nome e o namespace do contrato de dados de coleção dependem do próprio tipo de coleção. Por exemplo, consulte o tipo a seguir.

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

Quando serializado, o XML resultante é semelhante ao código a seguir.

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

Observe que não é mais equivalente à representação XML dos tipos não personalizados.

  • Você pode usar as propriedades Name e Namespace para personalizar ainda mais a nomenclatura. Considere a seguinte classe:

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

O XML resultante é semelhante ao código a seguir.

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

Para obter mais informações, consulte a seção "Regras de coleção avançadas" abaixo neste tópico.

Personalizando o nome do elemento de repetição em coleções de listas

As coleções de lista contêm entradas recorrentes. Normalmente, cada entrada repetida é representada como um elemento nomeado de acordo com o nome do contrato de dados do tipo contido na coleção.

Nos exemplos CustomerList, as coleções continham cadeias de caracteres. O nome do contrato de dados para o tipo primitivo de cadeia de caracteres é "string", portanto, o elemento repetido era "<string>".

No entanto, usando a propriedade ItemName no atributo CollectionDataContractAttribute, esse nome de elemento repetido pode ser personalizado. Por exemplo, consulte o tipo a seguir.

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

O XML resultante é semelhante ao código a seguir.

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

O namespace do elemento repetido é sempre o mesmo que o namespace do contrato de dados da coleção, que pode ser personalizado usando a propriedade Namespace, conforme descrito anteriormente.

Personalizando coleções de dicionários

As coleções de dicionários são principalmente listas de entradas, em que cada entrada tem uma chave seguida de um valor. Assim como acontece com as listas comuns, você pode alterar o nome do elemento que corresponde ao elemento repetido pela propriedade ItemName.

Além disso, você pode alterar os nomes do elemento que representam a chave e o valor usando as propriedades KeyName e ValueName. Os namespaces desses elementos são os mesmos que o namespace do contrato de dados de coleção.

Por exemplo, consulte o tipo a seguir.

[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

Quando serializado, o XML resultante é semelhante ao código a seguir.

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

Para obter mais informações sobre as coleções de dicionário, consulte a seção "Regras de coleção avançadas" abaixo neste tópico.

Coleções e Tipos conhecidos

Não é preciso adicionar tipos de coleção a tipos conhecidos quando usados de forma polimórfica no lugar de outras coleções ou interfaces de coleção. Por exemplo, se você declarar um membro de dados do tipo IEnumerable e usá-lo para enviar uma instância de ArrayList, não será necessário adicionar ArrayList a tipos conhecidos.

Quando você usa coleções polimorficamente no lugar de tipos que não são coleção, elas devem ser adicionadas a tipos conhecidos. Por exemplo, se você declarar um membro de dados do tipo Object e usá-lo para enviar uma instância de ArrayList, adicione ArrayList a tipos conhecidos.

Assim, não é permitido que você serialize qualquer coleção equivalente polimorficamente. Por exemplo, quando você adiciona ArrayList à lista de tipos conhecidos no exemplo anterior, não é permitido atribuir a classe Array of Object, mesmo que tenha um contrato de dados equivalente. Não é diferente do comportamento de tipos conhecidos comuns na serialização para tipos que não são de coleção, mas é importante principalmente para entender o caso de coleções porque é muito comum que as coleções sejam equivalentes.

Durante a serialização, apenas um tipo pode ser conhecido em qualquer escopo determinado para um certo contrato de dados e coleções equivalentes têm os mesmos contratos de dados. Isso significa que, no exemplo anterior, você não pode adicionar ArrayList e Array of Object a tipos conhecidos no mesmo escopo. Mais uma vez, isso é equivale ao comportamento de tipos conhecidos para tipos que não são de coleção, mas é importante principalmente para entender as coleções.

Tipos conhecidos também podem ser exigidos nos conteúdos de coleções. Por exemplo, se uma ArrayList realmente contém instâncias de Type1 e Type2, os dois tipos devem ser adicionados a tipos conhecidos.

O exemplo a seguir mostra um grafo de objetos construídos corretamente usando coleções e tipos conhecidos. O exemplo é um pouco inventado, porque em um aplicativo real você normalmente não definiria os membros de dados a seguir como Object e, portanto, não tem problemas de polimorfismo/tipos conhecidos.

[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

Na desserialização, se o tipo declarado for um tipo de coleção, ele cria uma instância independente do tipo que foi realmente enviado. Se o tipo declarado for uma interface de coleção, o desserializador escolhe um tipo para criar uma instância sem considerar tipos conhecidos.

Também na desserialização, se o tipo declarado não for um tipo de coleção, mas um tipo de coleção estiver sendo enviado, um tipo de coleção correspondente é escolhido da lista de tipos conhecidos. É possível adicionar tipos de interface de coleção à lista de tipos conhecidos na desserialização. Nesse caso, o mecanismo de desserialização escolhe novamente um tipo para criar uma instância.

Coleções e classe NetDataContractSerializer

Quando a classe NetDataContractSerializer está em uso, os tipos de coleção não personalizados (sem o atributo CollectionDataContractAttribute) que não são matrizes perdem seu significado especial.

Tipos de coleção não personalizados marcados com o atributo SerializableAttribute ainda podem ser serializados pela classe NetDataContractSerializer de acordo com o atributo SerializableAttribute ou as regras de interface ISerializable.

Tipos de coleção personalizados, interfaces de coleção e matrizes ainda são tratados como coleções, mesmo quando a classe NetDataContractSerializer está em uso.

Coleções e Esquema

Todas as coleções equivalentes têm a mesma representação no esquema XSD (linguagem de definição de esquema XML). Por isso, normalmente você não consegue o mesmo tipo de coleção no código do cliente gerado como aquele no servidor. Por exemplo, o servidor pode usar um contrato de dados com um List<T> genérico de membros de dados Inteiros, mas no código do cliente gerado, o mesmo membro de dados pode se tornar uma matriz de inteiros.

Coleções de dicionários são marcadas com uma anotação de esquema específico do WCF que indica que são dicionários. Caso contrário, eles não podem ser distinguidos de listas simples que contêm entradas com uma chave e um valor. Para obter uma descrição exata de como as coleções são representadas no esquema do contrato de dados, consulte Referência do esquema de contrato de dados.

Por padrão, os tipos não são gerados para coleções não personalizadas no código importado. Os membros de dados dos tipos de coleção de listas são importados como matrizes e os membros de dados de tipos de coleção de dicionários são importados como Dicionário Genérico.

No entanto, para coleções personalizadas, são gerados tipos separados, marcados com o atributo CollectionDataContractAttribute. (Um tipo de coleção personalizada no esquema é aquele que não usa o namespace padrão, o nome, o nome do elemento repetido ou os nomes de elemento chave/valor.) Esses tipos são tipos vazios que derivam de List<T> Genérico para tipos de lista e Dicionário Genérico para tipos de dicionário.

Por exemplo, você pode ter os seguintes tipos no servidor.

[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

Quando o esquema é exportado e importado novamente, o código do cliente gerado é semelhante ao seguinte código (os campos são mostrados no lugar das propriedades para facilitar a leitura).

[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

Talvez você queira usar tipos diferentes no código gerado do que aqueles que são padrões. Por exemplo, talvez você queira usar BindingList<T> Genérico em vez de matrizes regulares para que seus membros de dados tenham facilidade em associá-los aos componentes da interface do usuário.

Para escolher tipos de coleção a serem gerados, passe uma lista de tipos de coleção que você deseja usar na propriedade ReferencedCollectionTypes do objeto ImportOptions ao importar o esquema. Esses tipos são chamados tipos de coleção de referência.

Quando os tipos genéricos são referências, devem ser genéricos totalmente abertos ou genéricos totalmente fechados.

Observação

Ao usar a ferramenta Svcutil.exe, essa referência pode ser feita usando o comutador de linha de comando /collectionType (forma curta: /ct). Lembre-se de que você também deve especificar o assembly para os tipos de coleção de referência usando o comutador /reference (forma curta: /r). Se o tipo for genérico, ele deve ser seguido por acento grave e o número de parâmetros genéricos. O acento grave (`) não deve ser confundido com o caractere de aspa única (‘). Você pode especificar vários tipos de coleção de referência usando o comutador /collectionType mais de uma vez.

Por exemplo, permitir que todas as listas sejam importadas como List<T> Genérica.

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

Ao importar qualquer coleção, essa lista de tipos de coleção de referência é verificada e a coleção de melhor correspondência é usada se for encontrada, seja como um tipo de membro de dados (para coleções não personalizadas) ou como um tipo base para derivação (para coleções personalizadas). Os dicionários somente correspondem a dicionários, enquanto as listas são correspondentes às listas.

Por exemplo, se você adicionar a BindingList<T> Genérica e Hashtable à lista de tipos de referência, o código do cliente gerado para o exemplo anterior será semelhante ao exemplo a seguir:

[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

É possível especificar tipos de interface de coleção como parte de seus tipos de coleção de referência, mas não é possível especificar tipos de coleção inválidos (como aqueles que não têm nenhum método Add ou construtor público).

Um genérico fechado é considerado a melhor correspondência. (Tipos não genéricos são considerados equivalentes a genéricos fechados de Object). Por exemplo, se os tipos List<T> Genérica de DateTime, BindingList<T> Genérica (genérico aberto) e ArrayList são os tipos de coleção de referência, é gerado o que se segue:

[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

Para coleções de listas, há suporte apenas para os casos da tabela a seguir.

Tipo de referência Interface implementada por tipo de referência Exemplo Tipo tratado como:
Não genérico ou genérico fechado (qualquer quantidade de parâmetros) Não genérico MyType : IList

ou

MyType<T> : IList

em que T = int
Genérico fechado de Object (por exemplo, IList<object>)
Não genérico ou genérico fechado (qualquer quantidade de parâmetros que não correspondem necessariamente ao tipo de coleção) Genérico fechado MyType : IList<string>

ou

MyType<T> : IList<string> em que T=int
Genérico fechado (por exemplo, IList<string>)
Genérico fechado com qualquer quantidade de parâmetros Genérico aberto usando qualquer um dos parâmetros do tipo MyType<T,U,V> : IList<U>

em que T=int, U=string, V=bool
Genérico fechado (por exemplo, IList<string>)
Genérico aberto com um parâmetro Genérico aberto com um parâmetro de tipo MyType<T> : IList<T>, T é aberto Genérico aberto (por exemplo, IList<T>)

Se um tipo implementar mais de uma interface de coleção de listas, as seguintes restrições se aplicam:

  • Se o tipo implementar IEnumerable<T> Genérico (ou suas interfaces derivadas) várias vezes para tipos diferentes, o tipo não será considerado um tipo de coleção de referência válido e será ignorado. Isso é verdade mesmo que algumas implementações sejam inválidas ou usem genéricos abertos. Por exemplo, um tipo que implementa IEnumerable<T> Genérico de int e IEnumerable<T> Genérico de T nunca seria usado como uma coleção de referência de int ou qualquer outro tipo, independente de o tipo ter um método Add que aceita int ou um método Add que aceita um parâmetro do tipo T ou ambos.

  • Se o tipo implementar uma interface de coleção genérica, bem como IList, o tipo nunca será usado como um tipo de coleção de referência, a menos que a interface de coleção genérica seja um genérico fechado do tipo Object.

Para coleções dicionário, há suporte apenas para os casos da tabela a seguir.

Tipo de referência Interface implementada por tipo de referência Exemplo Tipo tratado como:
Não genérico ou genérico fechado (qualquer quantidade de parâmetros) IDictionary MyType : IDictionary

ou

MyType<T> : IDictionary em que T=int
Genérico fechado IDictionary<object,object>
Genérico fechado (qualquer quantidade de parâmetros) IDictionary<TKey,TValue>, fechado MyType<T> : IDictionary<string, bool> em que T=int Genérico fechado (por exemplo, IDictionary<string,bool>)
Genérico fechado (qualquer quantidade de parâmetros) IDictionary<TKey,TValue> Genérico, um de chave ou valor é fechado, o outro é aberto e usa um dos parâmetros do tipo MyType<T,U,V> : IDictionary<string,V> em que T=int, U=float,V=bool

ou

MyType<Z> : IDictionary<Z,bool> em que Z=string
Genérico fechado (por exemplo, IDictionary<string,bool>)
Genérico fechado (qualquer quantidade de parâmetros) IDictionary<TKey,TValue> Genérico, chave e valor estão abertos e cada um usa um dos parâmetros do tipo MyType<T,U,V> : IDictionary<V,U> em que T=int, U=bool, V=string Genérico fechado (por exemplo, IDictionary<string,bool>)
Genérico aberto (dois parâmetros) IDictionary<TKey,TValue> Genérico, aberto, usa os dois parâmetros genéricos do tipo na ordem em que aparecem MyType<K,V> : IDictionary<K,V>, K eV abertos Genérico aberto (por exemplo, IDictionary<K,V>)

Se o tipo implementar tanto IDictionary e IDictionary<TKey,TValue> Genérico, somente IDictionary<TKey,TValue> Genérico será considerado.

Não há suporte para referência de tipos genéricos parciais.

Não são permitidas duplicatas. Por exemplo, você não pode adicionar List<T> Genérica de Integer e Coleção Genérica de Integer para ReferencedCollectionTypes, porque torna-se impossível determinar qual delas usar quando uma lista de inteiros é encontrada no esquema. As duplicatas são detectadas somente se houver um tipo no esquema que mostre o problema de duplicatas. Por exemplo, se o esquema importado não contiver listas de inteiros, é permitido ter List<T> Genérica de Integer e a Coleção Genérica de Integer em ReferencedCollectionTypes, mas nenhum deles terá efeito.

Regras avançadas de coleção

Serializando coleções

Veja a seguir uma lista de regras de coleção para serialização:

  • É permitida a combinação de tipos de coleção (com coleções de coleções). Matrizes irregulares são tratadas como coleções de coleções. Matrizes multidimensionais não têm suporte.

  • Matrizes de bytes e matrizes de XmlNode são tipos de matrizes especiais que são tratadas como primitivas, não coleções. A serialização de uma matriz de bytes gera um único elemento XML que contém uma parte de dados codificados em Base64, em vez de um elemento separado para cada byte. Para obter mais informações sobre como uma matriz de XmlNode é tratada, consulte XML e tipos de ADO.NET em contratos de dados. É claro que esses tipos especiais podem participar de coleções: uma matriz de matriz de bytes resulta em vários elementos XML, com cada um contendo uma parte dos dados codificados na Base64.

  • Se o atributo DataContractAttribute for aplicado a um tipo de coleção, o tipo será tratado como um tipo de contrato de dados regular, não como uma coleção.

  • Se um tipo de coleção implementar a interface IXmlSerializable, as seguintes regras serão aplicadas, considerando um tipo myType:IList<string>, IXmlSerializable:

    • Se o tipo declarado for IList<string>, o tipo será serializado como uma lista.

    • Se o tipo declarado for myType, o tipo será serializado como IXmlSerializable.

    • Se o tipo declarado for IXmlSerializable, ele será serializado como IXmlSerializable, mas somente se você adicionar myType à lista de tipos conhecidos.

  • As coleções são serializadas e desserializadas usando os métodos mostrados na tabela a seguir.

Tipo de coleção implementa Método(s) chamado(s) na serialização Método(s) chamado(s) na desserialização
IDictionary<TKey,TValue>genérico get_Keys, get_Values Adicionar genérico
IDictionary get_Keys, get_Values Add
IList<T>genérico Indexador de IList<T> genérica Adicionar genérico
ICollection<T>genérico Enumerador Adicionar genérico
IList IList Indexador Add
IEnumerable<T>genérico GetEnumerator Um método não estático chamado Add que usa um parâmetro do tipo apropriado (o tipo do parâmetro genérico ou um de seus tipos base). Esse método deve ser feito para que o serializador trate um tipo de coleção como uma coleção durante a serialização e a desserialização.
IEnumerable (e, portanto, ICollection, o que deriva dele) GetEnumerator Um método não estático chamado Add que usa um parâmetro do tipo Object. Esse método deve ser feito para que o serializador trate um tipo de coleção como uma coleção durante a serialização e a desserialização.

A tabela anterior lista interfaces de coleção em ordem de precedência decrescente. Isso significa, por exemplo, que, se um tipo implementa IList e IEnumerable<T>Genérico, a coleção é serializada e desserializada de acordo com as regras IList:

  • Na desserialização, todas as coleções são desserializadas criando primeiro uma instância do tipo chamando o construtor sem parâmetros, que deve estar presente para o serializador tratar um tipo de coleção como uma coleção durante a serialização e a desserialização.

  • Se a mesma interface de coleção genérica for implementada mais de uma vez (por exemplo, se um tipo implementar ICollection<T>genérica de Integer e ICollection<T> genérica de String) e nenhuma precedência superior for encontrada, a coleção não será tratada como uma coleção válida.

  • Os tipos de coleção podem ter o atributo SerializableAttribute aplicado a eles e podem implementar a interface ISerializable. Ambos são ignorados. No entanto, se o tipo não atender por completo aos requisitos de tipo de coleção (por exemplo, o método Add está ausente), o tipo não será considerado um tipo de coleção e, portanto, o atributo SerializableAttribute e a interface ISerializable serão usados para determinar se o tipo pode ser serializado.

  • A aplicação do atributo CollectionDataContractAttribute a uma coleção para personalizá-lo remove o mecanismo de fallback anterior SerializableAttribute. Em vez disso, se uma coleção personalizada não atender aos requisitos de tipo de coleção, uma exceção InvalidDataContractException será gerada. A cadeia de caracteres de exceção geralmente contém informações sobre o motivo de um determinado tipo que não é considerado uma coleção válida (nenhum método Add, nenhum construtor sem parâmetros e assim por diante). Por isso, muitas vezes é útil aplicar o atributo CollectionDataContractAttributepara fins de depuração.

Nomenclatura de coleção

Veja a seguir uma lista de regras de nomenclatura de coleção:

  • O namespace padrão para todos os contratos de dados de coleção de dicionários e para contratos de dados de coleção de listas que contêm tipos primitivos é http://schemas.microsoft.com/2003/10/Serialization/Arrays, a menos que seja substituído usando o Namespace. Os tipos mapeados para tipos XSD integrados, bem como os tipos char, Timespan e Guid, são considerados primitivos para essa finalidade.

  • O namespace padrão para tipos de coleção que contêm tipos não primitivos, a menos que seja substituído usando Namespace, é o mesmo que o namespace do contrato de dados do tipo contido na coleção.

  • O nome padrão para contratos de dados de coleção de listas, a menos que substituído usando Name, é a cadeia de caracteres "ArrayOf" combinada com o nome do contrato de dados do tipo contido na coleção. Por exemplo, o nome do contrato de dados para uma lista genérica de inteiros é "ArrayOfint". Lembre-se de que o nome do contrato de dados de Object é "anyType", portanto, o nome do contrato de dados de listas não genéricas como ArrayList é "ArrayOfanyType".

O nome padrão para contratos de dados de coleção de dicionário, a menos que substituído com Name, é a cadeia de caracteres "ArrayOfKeyValueOf" combinada com o nome do contrato de dados do tipo de chave seguido pelo nome do contrato de dados do tipo de valor. Por exemplo, o nome do contrato de dados para um Dicionário Genérico de Cadeia de Caracteres e Inteiro é "ArrayOfKeyValueOfstringint". Além disso, se os tipos de valor ou chave não forem tipos primitivos, um hash de namespace para os namespaces de contrato de dados dos tipos de valor e chave será acrescentado ao nome. Para obter mais informações sobre hash de namespace, consulte Nomes de contratos de dados.

Cada contrato de dados de coleção de dicionários tem um contrato de dados complementar que representa uma entrada no dicionário. Seu nome é o mesmo que para o contrato de dados do dicionário, exceto para o prefixo "ArrayOf" e seu namespace é o mesmo do contrato de dados do dicionário. Por exemplo, para o contrato de dados do dicionário "ArrayOfKeyValueOfstringint", o contrato de dados "KeyValueofstringint" representa uma entrada no dicionário. É possível personalizar o nome desse contrato de dados usando a propriedade ItemName, conforme descrito na próxima seção.

As regras de nomenclatura de tipo genérico, conforme descrito em Nomes de Contrato de Dados, aplicam-se totalmente aos tipos de coleção. Ou seja, você pode usar entre chaves no Nome para indicar parâmetros de tipo genérico. No entanto, os números entre chaves referem-se a parâmetros genéricos e não a tipos contidos na coleção.

Personalização da coleção

Os seguintes usos do atributo CollectionDataContractAttribute são proibidos e geram uma exceção InvalidDataContractException:

Regras de polimorfismo

Como mencionado anteriormente, a personalização de coleções usando o atributo CollectionDataContractAttribute pode interferir na intercambiabilidade da coleção. Dois tipos de coleção personalizados somente podem ser considerados equivalentes se houver correspondência entre nome, namespace, nome do item e nomes de chave e valor (se forem coleções de dicionário).

Devido a personalizações, é possível usar inadvertidamente um contrato de dados de coleção no lugar de outro. Isso deve ser evitado. Observe a seguinte classe.

[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

Nesse caso, uma instância de Marks1 pode ser atribuída a testMarks. No entanto, Marks2 não deve ser usado porque seu contrato de dados não é considerado equivalente ao contrato de dados IList<int>. O nome do contrato de dados é "Marks2" e não "ArrayOfint", e o nome do elemento repetido é "<mark>" e não "<int>".

As regras na tabela a seguir se aplicam à atribuição polimórfica de coleções.

Tipo Declarado Atribuição de uma coleção não personalizada Atribuição de uma coleção personalizada
Objeto O nome do contrato é serializado. O nome do contrato é serializado.

A personalização é usada.
Interface de coleção O nome do contrato não é serializado. O nome do contrato não é serializado.

A personalização não é usada.*
Coleção não personalizada O nome do contrato não é serializado. O nome do contrato é serializado.

A personalização é usada.**
Coleção personalizada O nome do contrato é serializado. A personalização não é usada.** O nome do contrato é serializado.

A personalização do tipo atribuído é usada.**

*Com a classe NetDataContractSerializer, a personalização é usada nesse caso. A classe NetDataContractSerializer também serializa o nome de tipo real nesse caso, portanto, a desserialização funciona conforme o esperado.

**Esses casos geram instâncias inválidas de esquema e, portanto, devem ser evitados.

Nos casos em que o nome do contrato é serializado, o tipo de coleção atribuído deve estar na lista de tipos conhecidos. O contrário também é verdadeiro: nos casos em que o nome não é serializado, não é necessário adicionar o tipo à lista de tipos conhecidos.

Uma matriz de um tipo derivado pode ser atribuída a uma matriz de um tipo base. Nesse caso, o nome do contrato para o tipo derivado é serializado para cada elemento repetido. Por exemplo, se um tipo Book deriva do tipo LibraryItem, você pode atribuir uma matriz de Book a uma matriz de LibraryItem. Isso não se aplica a outros tipos de coleção. Por exemplo, você não pode atribuir uma Generic List of Book a uma Generic List of LibraryItem. No entanto, é possível atribuir uma Generic List of LibraryItem que contenha instâncias Book. No caso de matriz e não matriz, Book deve estar na lista de tipos conhecidos.

Coleções e preservação de referência de objeto

Quando um serializador funciona em um modo em que preserva referências de objeto, a preservação também se aplica a coleções. Especificamente, a identidade do objeto é preservada para coleções inteiras e itens individuais contidos em coleções. Para dicionários, a identidade do objeto é preservada tanto para os objetos de par chave/valor quanto para os objetos de chave e valor individuais.

Confira também