Partilhar via


Tipos de coleta 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 uma variedade de outros tipos (Lista Genérica, Genérica BindingList<T>StringCollection, ou ArrayList). Por exemplo, uma coleção pode conter uma lista de endereços de um determinado cliente. Essas coleções são chamadas de coleções de listas, independentemente de seu tipo real.

Existe uma forma especial de coleção que representa uma associação entre um item (a "chave") e outro (o "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ção pode mapear uma cidade ("chave") para sua população ("valor"). Essas coleções são chamadas de coleções de dicionários, independentemente de seu tipo real.

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

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

Requisitos adicionais sobre tipos de coleção, como ter um método chamado Add e um construtor sem parâmetros, são discutidos em detalhes 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 são suportadas diretamente, como o Generic ReadOnlyCollection<T> (porque ele não tem construtor sem parâmetros). No entanto, para obter informações sobre como contornar essas restrições, consulte a seção "Usando tipos de interface de coleção e coleções somente leitura" posteriormente neste tópico.

Os tipos contidos nas coleções devem ser tipos de contrato de dados ou ser serializáveis de outra forma. Para obter mais informações, consulte Tipos suportados pelo Data Contract Serializer.

Para obter mais informações sobre o que é e o que não é considerado uma coleção válida, bem como sobre como as coleções são serializadas, consulte as informações sobre a 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 como tendo o mesmo contrato de dados (a menos que sejam personalizadas usando o atributo, conforme discutido CollectionDataContractAttribute mais adiante neste tópico). Assim, por exemplo, os seguintes contratos de dados 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 projetado para ser vinculado a componentes da 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 como tendo o mesmo contrato de dados (a CollectionDataContractAttribute menos que sejam personalizadas pelo atributo).

Somente o tipo de contrato de dados é importante no que diz respeito à equivalência de coleta, não os 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.

Considera-se que as coleções não genéricas têm o mesmo contrato de dados que as coleções genéricas do tipo Object. (Por exemplo, os contratos de dados para ArrayList e Genérico List<T> de Object são os mesmos.)

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

Os tipos de interface de recolha (IEnumerable, IDictionary, genéricos IDictionary<TKey,TValue>ou interfaces derivadas destas interfaces) são também considerados como tendo contratos de dados de recolha, equivalentes aos contratos de dados de recolha para tipos de recolha reais. Assim, é possível declarar o tipo que está sendo serializado como um tipo de interface de coleção e os resultados são os mesmos como se um tipo de coleção real tivesse sido usado. Por exemplo, os seguintes contratos de dados 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 (ter um construtor sem parâmetros e um Add método) não se aplicam. Por exemplo, você pode definir endereços em Customer2 como uma instância de Generic ReadOnlyCollection<T> of Address, mesmo que não possa declarar diretamente um membro de dados do tipo Generic ReadOnlyCollection<T>.

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 é instanciado. O mecanismo de tipos conhecidos (descrito em Tipos conhecidos de contrato de dados) não tem efeito aqui, a escolha do tipo é incorporada ao WCF.

Personalizando tipos de coleção

Você pode personalizar tipos de coleção usando o CollectionDataContractAttribute atributo, que tem vários usos.

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

Nomenclatura do contrato de dados de coleta

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

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

  • Quando o CollectionDataContractAttribute atributo 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 namespace do próprio tipo de coleção. Para obter um exemplo, consulte os seguintes tipos.

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

O nome do contrato de dados de ambos os tipos é "ArrayOfstring" e não "CustomerList1" ou "StringList1". Isso significa que a serialização de qualquer um desses tipos no nível raiz produz 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. Isso torna possível a intercambiabilidade da coleção. Neste exemplo, CustomerList1 e StringList1 são completamente intercambiáveis.

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

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

Quando serializado, o XML resultante é semelhante ao seguinte.

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

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

  • Você pode usar as Name propriedades e Namespace para personalizar ainda mais a nomenclatura. Veja a aula a seguir.

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

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

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

Personalizando o nome do elemento repetitivo em coleções de lista

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

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

No entanto, usando a ItemName propriedade no atributo, esse nome de CollectionDataContractAttribute elemento repetitivo pode ser personalizado. Para obter um exemplo, consulte o seguinte tipo.

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

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

O namespace do elemento repetitivo é sempre o mesmo que o namespace do contrato de dados de coleta, que pode ser personalizado usando a Namespace propriedade, conforme descrito anteriormente.

Personalizando coleções de dicionários

As coleções de dicionários são essencialmente listas de entradas, onde cada entrada tem uma chave seguida de um valor. Assim como nas listas regulares, você pode alterar o nome do elemento que corresponde ao elemento repetitivo usando a ItemName propriedade.

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

Para obter um exemplo, consulte o seguinte tipo.

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

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

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

Coleções e tipos conhecidos

Não é necessário adicionar tipos de coleção a tipos conhecidos quando usados polimorficamente 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 de 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.

Isso não permite serializar qualquer coleção equivalente polimorficamente. Por exemplo, quando você adiciona ArrayList à lista de tipos conhecidos no exemplo anterior, isso não permite que você atribua a Array of Object classe, mesmo que ela tenha um contrato de dados equivalente. Isso não é diferente do comportamento de tipos conhecidos regulares na serialização para tipos que não são de coleção, mas é especialmente importante entender no 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 para um determinado contrato de dados, e coleções equivalentes têm todos os mesmos contratos de dados. Isso significa que, no exemplo anterior, não é possível adicionar ambos ArrayList e Array of Object a tipos conhecidos no mesmo escopo. Novamente, isso é equivalente ao comportamento de tipos conhecidos para tipos que não são de coleção, mas é especialmente importante entender para coleções.

Tipos conhecidos também podem ser necessários para o conteúdo de coleções. Por exemplo, se um ArrayList realmente contém instâncias de Type1 e Type2, ambos os tipos devem ser adicionados aos tipos conhecidos.

O exemplo a seguir mostra um gráfico de objeto construído corretamente usando coleções e tipos conhecidos. O exemplo é um pouco inventado, porque em um aplicativo real você normalmente não definiria os seguintes membros de dados como Object, e, portanto, não tem nenhum tipo conhecido / problemas de polimorfismo.

[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, o tipo declarado será instanciado independentemente do tipo que foi realmente enviado. Se o tipo declarado for uma interface de coleção, o desserializador selecionará um tipo a ser instanciado sem considerar os 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 será selecionado 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 novamente escolhe um tipo a ser instanciado.

Coleções e a classe NetDataContractSerializer

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

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

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

Coleções e esquema

Todas as coleções equivalentes têm a mesma representação no esquema XSD (XML Schema Definition Language). Devido a isso, você normalmente não obtém o mesmo tipo de coleção no código de cliente gerado como o do servidor. Por exemplo, o servidor pode usar um contrato de dados com um membro de dados Generic List<T> of Integer, mas no código de cliente gerado o mesmo membro de dados pode se tornar uma matriz de inteiros.

As coleções de dicionários são marcadas com uma anotação de esquema específica do WCF que indica que são dicionários; caso contrário, eles são indistinguíveis 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 de contrato de dados, consulte Referência de 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 lista são importados como matrizes e os membros de dados dos tipos de coleção de dicionário são importados como Dicionário Genérico.

No entanto, para coleções personalizadas, tipos separados são gerados, marcados com o CollectionDataContractAttribute atributo. (Um tipo de coleção personalizada no esquema é aquele que não usa o namespace padrão, nome, nome do elemento repetitivo ou nomes de elemento chave/valor.) Esses tipos são tipos vazios que derivam de Genérico List<T> 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 (campos são mostrados em vez de 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

Você pode querer usar tipos diferentes no código gerado do que os padrão. Por exemplo, você pode querer usar Genérico BindingList<T> em vez de matrizes regulares para seus membros de dados para facilitar a vinculação deles aos componentes da interface do usuário.

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

Quando os tipos genéricos estão a ser referenciados, devem ser genéricos totalmente abertos ou genéricos totalmente fechados.

Nota

Ao usar a ferramenta Svcutil.exe, essa referência pode ser realizada usando a opção 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 referenciados usando a opção /reference (forma abreviada: /r). Se o tipo for genérico, deve ser seguido por uma citação posterior e o número de parâmetros genéricos. A citação no verso (') não deve ser confundida com o caractere de aspas simples ('). Você pode especificar vários tipos de coleção referenciados usando a opção /collectionType mais de uma vez.

Por exemplo, para fazer com que todas as listas sejam importadas como Genérico List<T>.

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 referenciados é 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 do qual derivar (para coleções personalizadas). Os dicionários só são comparados com dicionários, enquanto as listas são comparadas com listas.

Por exemplo, se você adicionar o Generic BindingList<T> e Hashtable à lista de tipos referenciados, o código do cliente gerado para o exemplo anterior será semelhante ao seguinte.

[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

Você pode especificar tipos de interface de coleção como parte de seus tipos de coleção referenciados, mas não pode especificar tipos de coleção inválidos (como aqueles sem Add método ou construtor público).

Um genérico fechado é considerado a melhor combinação. (Os tipos não genéricos são considerados equivalentes aos genéricos fechados de Object). Por exemplo, se os tipos Genérico List<T> de , Genérico BindingList<T> (genérico aberto) e ArrayList forem os tipos de coleção referenciados, o seguinte DateTimeserá gerado.

[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, apenas os casos na tabela a seguir são suportados.

Tipo referenciado Interface implementada por tipo referenciado Exemplo Tipo tratado como:
Genéricos não genéricos ou fechados (qualquer número de parâmetros) Não genéricos MyType : IList

ou

MyType<T> : IList

em que T= int
Genérico fechado de Object (por exemplo, IList<object>)
Genérico não genérico ou fechado (qualquer número de parâmetros que não correspondam 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 número de parâmetros Abrir genérico 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>)
Abrir genérico com um parâmetro Abrir genérico usando o parâmetro do tipo MyType<T> : IList<T>, T está aberto Abrir genérico (por exemplo, IList<T>)

Se um tipo implementar mais de uma interface de coleta de lista, as seguintes restrições se aplicam:

  • Se o tipo implementa Generic IEnumerable<T> (ou suas interfaces derivadas) várias vezes para tipos diferentes, o tipo não é considerado um tipo de coleção referenciado válido e é ignorado. Isso é verdade mesmo se algumas implementações forem inválidas ou usarem genéricos abertos. Por exemplo, um tipo que implementa Generic IEnumerable<T> of e Generic IEnumerable<T> of T nunca seria usado como uma coleção referenciada de int ou qualquer outro tipo, independentemente de o tipo ter um Add método aceitando int ou um Add método aceitando um parâmetro do tipo Tint, ou ambos.

  • Se o tipo implementa uma interface de coleção genérica, bem como IList, o tipo nunca é usado como um tipo de coleção referenciado, a menos que a interface de coleção genérica seja um genérico fechado do tipo Object.

Para coleções de dicionários, apenas os casos na tabela a seguir são suportados.

Tipo referenciado Interface implementada por tipo referenciado Exemplo Tipo tratado como
Genéricos não genéricos ou fechados (qualquer número de parâmetros) IDictionary MyType : IDictionary

ou

MyType<T> : IDictionary em que T=int
Genérico fechado IDictionary<object,object>
Genérico fechado (qualquer número 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 número de parâmetros) Genérico IDictionary<TKey,TValue>, uma das chaves ou valores é fechada, a outra é aberta 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 número de parâmetros) Genérico IDictionary<TKey,TValue>, tanto a chave quanto o valor sã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>)
Abrir genérico (dois parâmetros) Generic IDictionary<TKey,TValue>, open, usa ambos os parâmetros genéricos do tipo na ordem em que aparecem MyType<K,V> : IDictionary<K,V>, K e V ambos abertos Abrir genérico (por exemplo, IDictionary<K,V>)

Se o tipo implementa ambos IDictionary e Genérico IDictionary<TKey,TValue>, apenas Genérico IDictionary<TKey,TValue> é considerado.

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

Duplicatas não são permitidas, por exemplo, você não pode adicionar o Genérico List<T> de Integer e a Coleção Genérica de Integer ao ReferencedCollectionTypes, porque isso torna impossível determinar qual usar quando uma lista de inteiros é encontrada no esquema. As duplicatas são detetadas somente se houver um tipo no esquema que exponha o problema de duplicatas. Por exemplo, se o esquema que está sendo importado não contiver listas de inteiros, é permitido ter o Genérico List<T> de Integer e a Coleção Genérica de Integer no ReferencedCollectionTypes, mas nenhum deles terá qualquer efeito.

Regras de Recolha Avançadas

Serializando coleções

A seguir está uma lista de regras de coleção para serialização:

  • É permitida a combinação de tipos de coleção (ter coleções de coleções). Matrizes irregulares são tratadas como coleções de coleções. Não há suporte para matrizes multidimensionais.

  • Matrizes de byte e matrizes de são tipos de XmlNode matriz especiais que são tratados como primitivos, não coleções. A serialização de uma matriz de bytes resulta em um único elemento XML que contém um bloco de dados codificados em Base64, em vez de um elemento separado para cada byte. Para obter mais informações sobre como uma matriz de é tratada, consulte XML e ADO.NET tipos em contratos de XmlNode 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 um pedaço de dados codificados em Base64.

  • Se o DataContractAttribute atributo 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 implementa a IXmlSerializable interface, as seguintes regras se aplicam, dado 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, ele 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.

Implementos de tipo de coleção Método(s) chamado(s) na serialização Método(s) chamado(s) na desserialização
Genéricos IDictionary<TKey,TValue> get_Keys, get_Values Adicionar genérico
IDictionary get_Keys, get_Values Add
Genéricos IList<T> Indexador genérico IList<T> Adicionar genérico
Genéricos ICollection<T> Recenseador Adicionar genérico
IList IList Indexador Add
Genéricos IEnumerable<T> 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 existir 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, 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 existir 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 as interfaces de coleta em ordem decrescente de precedência. Isso significa, por exemplo, que se um tipo implementa ambos IList e Generic IEnumerable<T>, a coleção é serializada e desserializada de acordo com as IList regras:

  • 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 que o serializador trate 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 Generic ICollection<T> of Integer e Generic ICollection<T> of String) e nenhuma interface de 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 SerializableAttribute atributo aplicado a eles e podem implementar a ISerializable interface. Ambos são ignorados. No entanto, se o tipo não atender totalmente aos requisitos de tipo de coleção (por exemplo, o Add método está faltando), o tipo não é considerado um tipo de coleção e, portanto, o SerializableAttribute atributo e a ISerializable interface são usados para determinar se o tipo pode ser serializado.

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

Nomenclatura da coleção

A seguir está uma lista de regras de nomenclatura de coleção:

  • O namespace padrão para todos os contratos de dados de coleta de dicionário, bem como para contratos de dados de coleta de lista que contêm tipos primitivos, é http://schemas.microsoft.com/2003/10/Serialization/Arrays a menos que seja substituído usando Namespace. Os tipos mapeados para tipos XSD internos, bem como char, Timespane Guid tipos, 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 de contrato de dados do tipo contido na coleção.

  • O nome padrão para contratos de dados de coleta de lista, 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 é "anyType", portanto, o nome do Object contrato de dados de listas não genéricas como ArrayList é "ArrayOfanyType".

O nome padrão para contratos de dados de coleta de dicionário, a menos que substituído usando 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 a chave ou os tipos de valor não forem tipos primitivos, um hash de namespace dos namespaces de contrato de dados dos tipos de chave e valor será anexado ao nome. Para obter mais informações sobre hashes de namespace, consulte Nomes de contratos de dados.

Cada contrato de dados de coleta de dicionário tem um contrato de dados complementares que representa uma entrada no dicionário. Seu nome é o mesmo que para o contrato de dados de dicionário, exceto para o prefixo "ArrayOf", e seu namespace é o mesmo que para o contrato de dados de dicionário. Por exemplo, para o contrato de dados do dicionário "ArrayOfKeyValueOfstringint", o contrato de dados "KeyValueofstringint" representa uma entrada no dicionário. Você pode personalizar o nome deste contrato de dados usando a ItemName propriedade, conforme descrito na próxima seção.

As regras genéricas de nomenclatura de tipos, conforme descritas em Nomes de Contratos de Dados, aplicam-se totalmente aos tipos de coleção, ou seja, você pode usar chaves em Name para indicar parâmetros de tipo genéricos. No entanto, os números dentro das 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 CollectionDataContractAttribute atributo são proibidos e resultam em uma InvalidDataContractException exceção:

Regras de Polimorfismo

Como mencionado anteriormente, personalizar coleções usando o CollectionDataContractAttribute atributo pode interferir na intercambiabilidade de coleções. Dois tipos de coleção personalizados só podem ser considerados equivalentes se seu nome, namespace, nome do item, bem como nomes de chave e valor (se forem coleções de dicionário) corresponderem.

Devido às personalizações, é possível usar inadvertidamente um contrato de dados de coleta onde outro é esperado. Esta situação deve ser evitada. Veja os tipos a seguir.

[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 utilizado porque o seu contrato de dados não é considerado equivalente ao IList<int> contrato de dados. O nome do contrato de dados é "Marks2" e não "ArrayOfint", e o nome do elemento repetitivo é "<mark>" e não "<int>".

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

Tipo declarado Atribuir uma coleção não personalizada Atribuição de uma coleção personalizada
Object O nome do contrato é serializado. O nome do contrato é serializado.

A personalização é usada.
Interface de recolha 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, a NetDataContractSerializer personalização é usada neste caso. A NetDataContractSerializer classe também serializa o nome do tipo real nesse caso, portanto, a desserialização funciona conforme o esperado.

**Estes casos resultam em instâncias inválidas do 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, adicionar o tipo à lista de tipos conhecidos não é necessário.

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 de repetição. Por exemplo, se um tipo Book deriva do tipo LibraryItem, você pode atribuir uma matriz de a uma matriz de BookLibraryItem. Isso não se aplica a outros tipos de coleção. Por exemplo, não é possível atribuir um Generic List of Book a um Generic List of LibraryItemarquivo . No entanto, você pode atribuir um Generic List of LibraryItem que contenha Book instâncias. Tanto no caso de matriz quanto no caso de não-matriz, Book deve estar na lista de tipos conhecidos.

Preservação de coleções e referências de objetos

Quando um serializador funciona em um modo em que preserva referências de objeto, a preservação de referência de objeto 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 do par chave/valor quanto para os objetos individuais de chave e valor.

Consulte também