Tipos de colección en contratos de datos

Una colección es una lista de elementos de un determinado tipo. En .NET Framework, tales listas se pueden representar mediante matrices o una variedad de otros tipos (Lista genérica, BindingList<T> genérica, StringCollection o ArrayList). Por ejemplo, una colección puede albergar una lista de direcciones para un determinado cliente. Estas colecciones se denominan colecciones de lista, con independencia de cual sea su tipo real.

Existe una forma especial de colección que representa una asociación entre un elemento (la "clave") y otro (el "valor"). En .NET Framework, se representan mediante tipos como Hashtable y el diccionario genérico. Por ejemplo, una colección de asociaciones puede asignar una ciudad ("clave") a su población ("valor"). Estas colecciones se denominan colecciones de diccionario, con independencia de cual sea su tipo real.

Las colecciones reciben un tratamiento especial en el modelo del contrato de datos.

Los tipos que implementan la interfaz IEnumerable , incluyendo las matrices y las colecciones genéricas, se reconocen como colecciones. De entre ellos, los tipos que implementan IDictionary o las interfaces IDictionary<TKey,TValue> genéricas son colecciones de diccionarios; todos los otros son colecciones de listas.

Los requisitos adicionales en los tipos de colección, como tener un método llamado Add y un constructor sin parámetros, se explican con detalles en las secciones siguientes. Esto garantiza que los tipos de colección se puedan tanto serializar como deserializar. Esto significa que algunas colecciones no se admiten directamente, por ejemplo ReadOnlyCollection<T> genérica (porque no tiene ningún constructor sin parámetros). Sin embargo, para información sobre cómo burlar estas restricciones, vea la sección "Utilizar tipos de interfaz de colección y colecciones de solo lectura" a continuación de este tema.

Los tipos contenidos en las colecciones deben ser tipos de contrato de datos o, de lo contrario, se deben poder serializar. Para más información, consulte Tipos admitidos por el serializador de contratos de datos.

Para más información sobre lo que se considera y lo que no se considera una colección válida, así como sobre cómo se serializan las colecciones, vea la información sobre la serialización de colecciones en la sección "Reglas avanzadas de colección" de este tema.

Colecciones intercambiables

Se considera que todas las colecciones de listas del mismo tipo tienen el mismo contrato de datos (a menos que se personalicen utilizando el atributo CollectionDataContractAttribute , como se aborda más adelante en este tema). Así, por ejemplo, los contratos de datos siguientes son 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 contratos de datos generan XML similar al código siguiente.

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

La intercambiabilidad de colecciones permite utilizar, por ejemplo, un tipo de colección optimizado para rendimiento en el servidor y un tipo de colección diseñado para estar enlazado a los componentes de la interfaz de usuario en el cliente.

De manera similar a las colecciones de listas, se considera que todas las colecciones de diccionarios que tienen la misma clave y tipos de valor tienen el mismo contrato de datos (a menos que se personalicen utilizando el atributo CollectionDataContractAttribute ).

Solo el tipo de contrato de datos importa en lo relativo a la equivalencia de colección, no los tipos .NET. Es decir, una colección de Type1 se considera equivalente a una colección de Type2 si Type1 y Type2 tienen contratos de datos equivalentes.

Se considera que las colecciones no genéricas tienen el mismo contrato de datos que las colecciones genéricas de tipo Object. (Por ejemplo, los contratos de datos para ArrayList y la List<T> genérica de Object son los mismos.)

Utilizar tipos de interfaz de colección y colecciones de solo lectura

Los tipos de interfaz de colección (IEnumerable, IDictionary, IDictionary<TKey,TValue>genérico, o interfaces derivadas de estas interfaces) también se considera que tienen contratos de datos de colección, equivalentes a los contratos de datos de colección para los tipos de colección reales. Por lo tanto, es posible declarar el tipo que se está serializando como un tipo de interfaz de colección y los resultados son los mismos que si se hubiese utilizado un tipo de colección real. Por ejemplo, los contratos de datos siguientes son 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 la serialización, cuando el tipo declarado es una interfaz, el tipo de instancia real que se usa puede ser cualquier tipo que implemente esa interfaz. Las restricciones que se explican previamente (tener un constructor sin parámetros y un método Add) no se aplican. Por ejemplo, puede establecer direcciones en Customer2 en una instancia de ReadOnlyCollection<T> genérica de dirección, aunque no pueda declarar directamente un miembro de datos de tipo ReadOnlyCollection<T>genérica.

Durante la deserialización, cuando el tipo declarado es una interfaz, el motor de serialización elige un tipo que implemente la interfaz declarada y se crea una instancia del tipo. El mecanismo de tipos conocidos (que se describe en Tipos conocidos de contratos de datos) no tiene ningún efecto aquí, y la opción de tipo está integrada en WCF.

Personalizar tipos de colección

Puede personalizar tipos de colección utilizando el atributo CollectionDataContractAttribute , que tiene varios usos.

Observe que al personalizar los tipos de colección se pone en peligro la posibilidad de intercambio de colecciones, por lo que normalmente se recomienda evitar aplicar este atributo siempre que sea posible. Para más información sobre este problema, vea la sección "Reglas avanzadas de colección" más adelante en este mismo tema.

Denominación de contrato de datos de colección

Las reglas para denominar los tipos de colección son similares a las existentes para denominar los tipos de contrato de datos normales, como se describe en Data Contract Names, aunque existen algunas diferencias importantes:

  • El atributo CollectionDataContractAttribute se utiliza para personalizar el nombre, en lugar del atributo DataContractAttribute . El atributo CollectionDataContractAttribute también tiene propiedades Name y Namespace .

  • Cuando no se aplica el atributo CollectionDataContractAttribute , el nombre predeterminado y el espacio de nombres para los tipos de colección dependen de los nombres y espacios de nombres de tipos contenidos dentro de la colección. No se ven afectados por el nombre ni el espacio de nombres del propio tipo de colección. Para obtener un ejemplo, vea los tipos siguientes.

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

El nombre del contrato de datos de ambos tipos es "ArrayOfstring" y no "CustomerList1" o "StringList1". Esto significa que la serialización de uno de estos tipos en el nivel raíz aporta XML similar al código siguiente.

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

Esta regla de nomenclatura se eligió para garantizar que un tipo no personalizado que represente una lista de cadenas tenga el mismo contrato de datos y la misma representación XML. Esto posibilita la intercambiabilidad de colecciones. En este ejemplo, CustomerList1 y StringList1 son completamente intercambiables.

Sin embargo, cuando se aplica el atributo CollectionDataContractAttribute , la colección se vuelve un contrato de datos de colección personalizado, aunque no se haya establecido ninguna propiedad en el atributo. El nombre y espacio de nombres del contrato de datos de colección dependen del propio tipo de colección. Para ver un ejemplo, vea el tipo siguiente.

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

Al serializar, el XML resultante es similar al siguiente.

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

Tenga en cuenta que ha dejado de ser equivalente a la representación XML de los tipos no personalizados.

  • Puede utilizar las propiedades Name y Namespace para personalizar más la denominación. Vea la siguiente clase.

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

El XML resultante es similar al siguiente.

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

Para más información, vea la sección "Reglas avanzadas de colección" más adelante en este mismo tema.

Personalizar el nombre de elementos repetidos en colecciones de listas

Las colecciones de listas contienen entradas repetidas. Normalmente, cada entrada repetida se representa como un elemento denominado según el nombre del contrato de datos del tipo contenido en la colección.

En los ejemplos CustomerList , las colecciones contenían cadenas. El nombre del contrato de datos para el tipo primitivo de la cadena es "string", por lo que el elemento repetido era "<string>".

Sin embargo, utilizando la propiedad ItemName en el atributo CollectionDataContractAttribute , se puede personalizar este nombre de elementos repetidos. Para ver un ejemplo, vea el tipo siguiente.

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

El XML resultante es similar al siguiente.

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

El espacio de nombres del elemento repetido es siempre igual al espacio de nombres del contrato de datos de la colección, que se puede personalizar mediante la propiedad Namespace , como se comentaba anteriormente.

Personalizar las colecciones de diccionarios

Las colecciones de diccionarios son esencialmente listas de entradas, donde cada entrada tiene una clave seguida de un valor. Al igual que sucede con las listas normales, puede cambiar el nombre de elemento que corresponde al elemento repetido mediante la propiedad ItemName .

Además, puede cambiar los nombres de elemento que representan la clave y el valor utilizando las propiedades KeyName y ValueName . Los espacios de nombres para estos elementos son los mismos que el espacio de nombres del contrato de datos de colección.

Para ver un ejemplo, vea el tipo siguiente.

[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

Al serializar, el XML resultante es similar al siguiente.

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

Para más información sobre las colecciones de diccionario, vea la sección "Reglas avanzadas de colección" más adelante en este mismo tema.

Colecciones y tipos conocidos

No necesita agregar tipos de colección a los tipos conocidos al utilizarlos polimórficamente en lugar de otras colecciones o interfaces de colección. Por ejemplo, si declara un miembro de datos de tipo IEnumerable y lo utiliza para enviar una instancia de ArrayList, no necesita agregar ArrayList a los tipos conocidos.

Si se usan colecciones polimórficamente en lugar de tipos de no colección, deben agregarse a tipos conocidos. Por ejemplo, si declara un miembro de datos de tipo Object y lo usa para enviar una instancia de ArrayList, necesita agregar ArrayList a los tipos conocidos.

Esto no le permite serializar polimórficamente cualquier colección equivalente. Por ejemplo, el hecho de agregar ArrayList a la lista de tipos conocidos en el ejemplo anterior no le permite asignar la clase Array of Object , aunque tenga un contrato de datos equivalente. Este comportamiento no es diferente al de los tipos conocidos normales en la serialización de tipos de no colección, pero es especialmente importante tenerlo claro en el caso de las colecciones, porque es muy frecuente que las colecciones sean equivalentes.

Durante la serialización, solo un tipo puede ser conocido en un ámbito dado para un contrato de datos determinado, y todas las colecciones equivalentes tienen los mismos contratos de datos. Esto significa que, en el ejemplo anterior, no se puede agregar ArrayList y Array of Object a tipos conocidos en el mismo ámbito. De nuevo, esto equivale al comportamiento de tipos conocidos para los tipos de no colección, pero es especialmente importante entenderlo para las colecciones.

Los tipos conocidos también se pueden requerir para el contenido de colecciones. Por ejemplo, si ArrayList realmente contiene instancias de Type1 y Type2, estos dos tipos se deberían agregar a los tipos conocidos.

El ejemplo siguiente muestra un gráfico de objeto correctamente construido utilizando colecciones y tipos conocidos. (El ejemplo está un poco retocado, porque en una aplicación real normalmente no se definirían los miembros de datos siguientes como Objecty, por consiguiente, no tendría problemas con los tipos conocidos o 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

Durante la deserialización, si el tipo declarado es un tipo de colección, se crea la instancia del tipo declarado independientemente del tipo que realmente se envió. Si el tipo declarado es una interfaz de colección, el deserializador escoge un tipo a partir del cual se va a crear una instancia independientemente de los tipos conocidos.

También durante la deserialización, si el tipo declarado no es un tipo de colección pero se está enviando un tipo de colección, se seleccionará un tipo de colección coincidente de la lista de tipos conocidos. Es posible agregar los tipos de interfaz de colección a la lista de tipos conocidos durante la deserialización. En este caso, el motor de la deserialización escoge de nuevo un tipo a partir del cual se va a crear una instancia.

Colecciones y la clase NetDataContractSerializer

Cuando se usa la clase NetDataContractSerializer , los tipos de colección no personalizados (sin el atributo CollectionDataContractAttribute ) que no son matrices pierden su significado especial.

La clase SerializableAttribute todavía puede serializar los tipos de colección no personalizados marcados con el atributo NetDataContractSerializer de conformidad con el atributo SerializableAttribute o las reglas de interfaz ISerializable .

Los tipos de colección personalizados, las interfaces de colección y las matrices todavía se tratan como colecciones, incluso cuando la clase NetDataContractSerializer está en uso.

Colecciones y esquema

Todas las colecciones equivalentes tienen la misma representación en lenguaje de definición de esquemas XML (XSD). Como consecuencia, normalmente no se obtiene el mismo tipo de colección en el código de cliente generado y en el servidor. Por ejemplo, el servidor puede utilizar un contrato de datos con una List<T> genérica de miembro de datos entero, pero en el código de cliente generado el mismo miembro de datos se puede convertir en una matriz de enteros.

Las colecciones de diccionario se marcan con una anotación de esquema específica de WCF que indica que son diccionarios; de lo contrario, no se diferencian de las listas simples que contienen entradas con una clave y un valor. Para una descripción exacta de cómo se representan las colecciones en el esquema del contrato de datos, consulte Data Contract Schema Reference.

De forma predeterminada, no se generan tipos para las colecciones no personalizadas en el código importado. Los miembros de datos de tipos de colección de listas se importan como matrices, y los miembros de datos de tipos de colección de diccionarios se importan como diccionario genérico.

Sin embargo, para las colecciones personalizadas, se generan tipos separados, marcados con el atributo CollectionDataContractAttribute . (Un tipo de colección personalizado en el esquema es aquel que no usa el espacio de nombres predeterminado, el nombre, el nombre del elemento repetido o los nombres de elementos clave-valor). Estos tipos son tipos vacíos que se derivan de List<T> genérica para tipos de lista y Diccionario genérico para tipos de diccionario.

Por ejemplo, puede tener los tipos siguientes en el 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

Cuando el esquema se exporta y se vuelve a importar, el código de cliente generado es similar al siguiente (para facilitar la lectura, se muestran los campos en lugar de las propiedades).

[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

Puede desear utilizar unos tipos en el código generado que sean diferentes a los predeterminados. Por ejemplo, puede desear utilizar BindingList<T> genérica en lugar de las matrices normales para sus miembros de datos para que sea más fácil enlazarlos a los componentes de la interfaz de usuario.

Para elegir los tipos de colección a generar, pase una lista de tipos de colección que desee utilizar en la propiedad ReferencedCollectionTypes del objeto ImportOptions al importar el esquema. Estos tipos se denominan tipos de colección de referencia.

Cuando se hace referencia a tipos genéricos, deben ser genéricos totalmente abiertos o genéricos totalmente cerrados.

Nota

Al usar la herramienta Svcutil.exe, la referencia se puede realizar mediante el uso del modificador de la línea de comandos /collectionType (forma corta: /ct). Tenga en cuenta que también debe especificar el ensamblado para los tipos de colección a los que se hace referencia mediante el uso del modificador /reference (forma corta: /r). Si el tipo es genérico, debe ir seguido de una comilla atrás y el número de parámetros genéricos. La comilla de atrás (`) no debe confundirse con el carácter de la comilla simple (‘). Puede especificar varios tipos de colección de referencia mediante el uso del modificador /collectionType más de una vez.

Por ejemplo, para que todas las listas se importen como de tipo List<T>genérico.

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

Al importar una colección, se escanea esta lista de tipos de colección de referencia y se utiliza la colección más coincidente (si se encuentra una), ya sea como un tipo de miembro de datos (para las colecciones no personalizadas) o como un tipo base del que derivar (para las colecciones personalizadas). Los diccionarios solo se confrontan con los diccionarios, mientras las listas se confrontan con las listas.

Por ejemplo, si agrega la BindingList<T> genérica y Hashtable a la lista de tipos de referencia, el código de cliente generado para el ejemplo anterior es similar al siguiente.

[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

Puede especificar los tipos de interfaz de colección como parte de sus tipos de colección de referencia, pero no puede especificar tipos de colección no válidos (como aquellos sin método Add o constructor público).

Se considera que un tipo genérico cerrado es el que más coincide. (Los tipos no genéricos se consideran equivalentes a los genéricos cerrados de Object.) Por ejemplo, si los tipos List<T> genéricos de DateTime, BindingList<T> genérico (abrir genérico), y ArrayList son los tipos de colección de referencia, se genera lo siguiente.

[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 las colecciones de lista, solo se admiten los casos de la tabla siguiente.

Tipo de referencia Interfaz implementada por el tipo de referencia Ejemplo Tipo tratado como:
No genérico o genérico cerrado (cualquier número de parámetros) No genérico MyType : IList

o

MyType<T> : IList

donde T= int
Genérico cerrado de Object (por ejemplo, IList<object>)
No genérico o genérico cerrado (cualquier número de parámetros que no necesariamente coincide con el tipo de colección) Genérico cerrado MyType : IList<string>

o

MyType<T> : IList<string> donde T=int
Genérico cerrado (por ejemplo, IList<string>)
Genérico cerrado con cualquier número de parámetros Abrir genérico utilizando cualquiera de los parámetros del tipo MyType<T,U,V> : IList<U>

donde T=int, U=string, V=bool
Genérico cerrado (por ejemplo, IList<string>)
Genérico abierto con un parámetro Abrir genérico utilizando el parámetro del tipo MyType<T> : IList<T>, T está abierto Genérico abierto (por ejemplo, IList<T>)

Si un tipo implementa más de una interfaz de colección de listas, se aplican las restricciones siguientes:

  • Si el tipo implementa varias veces IEnumerable<T> genérica (o sus interfaces derivadas) para los diferentes tipos, el tipo no se considera un tipo de colección de referencia válido y se ignora. Esto es verdad aun cuando algunas implementaciones no sean válidas o utilicen genéricos abiertos. Por ejemplo, un tipo que implementa IEnumerable<T> genérica de int y IEnumerable<T> genérica de T nunca se utilizaría como una colección de referencia de int o cualquier otro tipo, independientemente de si el tipo tiene un método Add que acepta int o un método Add que acepta un parámetro de tipo T, o ambos.

  • Si el tipo implementa una interfaz de colección genérica así como IList, el tipo nunca se usa como un tipo de colección de referencia a menos que la interfaz de colección genérica sea cerrada genérica de tipo Object.

Para las colecciones de diccionario, solo se admiten los casos de la tabla siguiente.

Tipo de referencia Interfaz implementada por el tipo de referencia Ejemplo Tipo tratado como
No genérico o genérico cerrado (cualquier número de parámetros) IDictionary MyType : IDictionary

o

MyType<T> : IDictionary donde T=int
Genérico cerrado IDictionary<object,object>
Genérico cerrado (cualquier número de parámetros) IDictionary<TKey,TValue>, cerrado MyType<T> : IDictionary<string, bool> donde T=int Genérico cerrado (por ejemplo, IDictionary<string,bool>)
Genérico cerrado (cualquier número de parámetros) IDictionary<TKey,TValue>genérico, o clave o valor está cerrado, el otro está abierto y utiliza uno de los parámetros de tipo MyType<T,U,V> : IDictionary<string,V> donde T=int, U=float, V=bool

o

MyType<Z> : IDictionary<Z,bool> donde Z=string
Genérico cerrado (por ejemplo, IDictionary<string,bool>)
Genérico cerrado (cualquier número de parámetros) IDictionary<TKey,TValue>genérico, tanto clave como valor están abiertos y cada uno utiliza uno de los parámetros del tipo MyType<T,U,V> : IDictionary<V,U> donde T=int, U=bool, V=string Genérico cerrado (por ejemplo, IDictionary<string,bool>)
Genérico abierto (dos parámetros) IDictionary<TKey,TValue>genérico, abrir, utiliza los dos parámetros genéricos del tipo en el orden que aparecen MyType<K,V> : IDictionary<K,V>, tanto K como V están abiertos Genérico abierto (por ejemplo, IDictionary<K,V>)

Si el tipo implementa tanto IDictionary y IDictionary<TKey,TValue>genérica, solo se considera IDictionary<TKey,TValue> genérica.

No permite hacer referencia a los tipos genéricos parciales.

No se permiten los duplicados; por ejemplo, no se puede agregar la colección genérica List<T> de Integer y la colección genérica de Integer a ReferencedCollectionTypes, porque sería imposible determinar cuál de las dos se debe usar cuando se encuentre una lista de enteros en el esquema. Los duplicados se detectan solo si hay un tipo en el esquema que expone el problema de los duplicados. Por ejemplo, si el esquema que se importa no contiene listas de enteros, está permitido tener tanto List<T> genérica de Integer como la colección genérica de Integer en ReferencedCollectionTypes, pero ninguna tiene efecto.

Reglas avanzadas de colección

Serialización de colecciones

A continuación, se muestra una lista de las reglas de colección para la serialización:

  • Se permite combinar los tipos de colección (tener colecciones de colecciones). Las matrices escalonadas se tratan como colecciones de colecciones. No se soportan matrices multidimensionales.

  • Las matrices de byte y matrices de XmlNode son los tipos de matriz especiales que se tratan como primitivas, no colecciones. Al serializar una matriz de bytes se obtiene un elemento XML único que contiene un bloque de datos con codificación Base64, en lugar de un elemento aparte para cada byte. Para más información sobre cómo se trata una matriz de XmlNode, consulte Tipos de XML y ADO.NET Types en contratos de datos. Evidentemente, estos tipos especiales pueden participar en colecciones: una matriz de matriz de byte resulta en múltiples elementos XML, y cada uno de ellos contiene un bloque de datos codificados por Base64.

  • Si el atributo DataContractAttribute se aplica a un tipo de colección, el tipo se trata como un tipo de contrato de datos normal, no como una colección.

  • Si un tipo de colección implementa la interfaz IXmlSerializable , se aplican las siguientes reglas dado un tipo myType:IList<string>, IXmlSerializable:

    • Si el tipo declarado es IList<string>, el tipo se serializa como una lista.

    • Si el tipo declarado es myType, se serializa como IXmlSerializable.

    • Si el tipo declarado es IXmlSerializable, se serializa como IXmlSerializable, pero solo si se agrega myType a la lista de tipos conocidos.

  • Las colecciones se serializan y deserializan con los métodos que se muestran en la tabla siguiente.

El tipo de colección implementa Los métodos llamados durante la serialización Métodos llamados durante la deserialización
Genérico IDictionary<TKey,TValue> get_Keys, get_Values Agregar genérico
IDictionary get_Keys, get_Values Add
Genérico IList<T> Indizador IList<T> genérico Agregar genérico
Genérico ICollection<T> Enumerador Agregar genérico
IList IList Indizador Add
Genérico IEnumerable<T> GetEnumerator Un método no estático llamado Add que toma un parámetro del tipo adecuado (el tipo del parámetro genérico o uno de sus tipos base). Este tipo de método debe existir para que el serializador pueda tratar un tipo de colección como una colección durante serialización y deserialización.
IEnumerable (y por consiguiente ICollection, que deriva de él) GetEnumerator Un método no estático llamado Add que toma un parámetro de tipo Object. Este tipo de método debe existir para que el serializador pueda tratar un tipo de colección como una colección durante serialización y deserialización.

En la tabla anterior se muestran las interfaces de colección en orden descendente de prioridad. Por ejemplo, esto significa que si un tipo implementa tanto IList como IEnumerable<T>genérica, la colección se serializa y deserializa según las reglas IList :

  • En la deserialización, todas las colecciones se deserializan creando primero una instancia del tipo llamando al constructor sin parámetros, que debe estar presente para que el serializador trate un tipo de colección como una colección durante la serialización y deserialización.

  • Si se implementa la misma interfaz de colección genérica más de una vez (por ejemplo, si un tipo implementa tanto ICollection<T> genérica de Integer como ICollection<T> genérica de String) y no se encuentra ninguna interfaz de la prioridad más alta, la colección no se trata como una colección válida.

  • Los tipos de colección pueden tener el atributo SerializableAttribute aplicado a ellos e implementar la interfaz ISerializable . Se ignoran ambos. Sin embargo, si el tipo no cumple totalmente los requisitos del tipo de colección (por ejemplo, no se encuentra el método Add ), no se considera un tipo de colección y, por consiguiente, se usan el atributo SerializableAttribute y la interfaz ISerializable para determinar si el tipo se puede serializar.

  • Al aplicar el atributo CollectionDataContractAttribute a una colección para personalizarla, se quita el mecanismo de reserva anterior de SerializableAttribute . En su lugar, si una colección personalizada no cumple los requisitos de tipo de colección, se produce una excepción InvalidDataContractException . La cadena de excepciones contiene a menudo información que explica por qué un tipo determinado no se considera una colección válida (ningún método Add, ningún constructor sin parámetros, etc.), por lo que a menudo resulta útil aplicar el atributo CollectionDataContractAttribute con fines de depuración.

Denominación de colección

A continuación, se muestra una lista de las reglas para denominar una colección:

  • El espacio de nombres predeterminado para todos los contratos de datos de colección de diccionarios, así como para los contratos de datos de colección de listas que contienen tipos primitivos, es http://schemas.microsoft.com/2003/10/Serialization/Arrays a menos que se invalide utilizando Espacio de nombres. Los tipos que se asignan a tipos XSD integrados, así como char, Timespany los tipos Guid , se consideran primitivos para este propósito.

  • El espacio de nombres predeterminado para tipos de colección que contienen tipos no primitivos, a menos que se invalide mediante el uso de Namespace, es el mismo que el espacio de nombres de contrato de datos del tipo contenido en la colección.

  • El nombre predeterminado para los contratos de datos de colección de listas, a menos que se invalide utilizando Nombre, es la cadena "ArrayOf" combinada con el nombre de contrato de datos del tipo contenido en la colección. Por ejemplo, el nombre de contrato de datos para una lista genérica de enteros es "ArrayOfint." Tenga presente que el nombre de contrato de datos de Object es "anyType", de modo que el nombre de contrato de datos de listas no genéricas como ArrayList es "ArrayOfanyType."

El nombre predeterminado para los contratos de datos de colección de diccionarios, a menos que se invalide utilizando Name, es la cadena "ArrayOfKeyValueOf" combinada con el nombre de contrato de datos del tipo clave seguido por el nombre de contrato de datos del tipo de valor. Por ejemplo, el nombre de contrato de datos para un diccionario genérico de cadena y entero es "ArrayOfKeyValueOfstringint." Adicionalmente, si los tipos de clave o valor no son tipos primitivos, un hash de espacio de nombres de los espacios de nombres de contrato de datos de los tipos de clave y valor se agrega al nombre. Para más información sobre los hash de espacio de nombres, consulte Nombres de contratos de datos.

Cada contrato de datos de colección de diccionarios tiene un contrato de datos complementario que representa una entrada en el diccionario. Su nombre es igual que para el contrato de datos del diccionario, salvo el prefijo "ArrayOf", y su espacio de nombres es igual que para el contrato de datos del diccionario. Por ejemplo, para el contrato de datos de diccionario de "ArrayOfKeyValueOfstringint", el contrato de datos de "KeyValueofstringint" representa una entrada en el diccionario. Puede personalizar el nombre de este contrato de datos utilizando la propiedad ItemName , como se describe en la sección siguiente.

Las reglas de denominación de tipo genérico, como se describen en Data Contract Names, se aplican totalmente a los tipos de colección; es decir, puede usar llaves dentro del nombre para indicar los parámetros de tipo genérico. Sin embargo, los números entre llaves hacen referencia a parámetros genéricos y no a tipos contenidos dentro de la colección.

Personalización de colección

Los usos siguientes del atributo CollectionDataContractAttribute están prohibidos y producen una excepción InvalidDataContractException :

Reglas del polimorfismo

Como se ha mencionado previamente, la personalización de colecciones mediante el atributo CollectionDataContractAttribute puede interferir con la intercambiabilidad de las colecciones. Dos tipos de colección personalizados solo se pueden considerar equivalentes si su nombre, espacio de nombres, nombre de elemento, así como nombres de clave y valor (si son colecciones de diccionarios) coinciden.

Debido a las personalizaciones, es posible utilizar inadvertidamente un contrato de datos de colección donde se espera otro. Éste debería evitarse. Vea los tipos siguientes.

[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

En este caso, una instancia de Marks1 se puede asignar a testMarks. Sin embargo, no se debería utilizar Marks2 porque su contrato de datos no se considera equivalente al contrato de datos IList<int> . El nombre de contrato de datos es "Marks2" y no "ArrayOfint" y el nombre de elemento repetido es "<mark>" y no "<int>".

Las reglas de la tabla siguiente se aplican a la asignación polimórfica de colecciones.

Tipo declarado Asignar una colección no personalizada Asignar una colección personalizada
Object El nombre del contrato está serializado. El nombre del contrato está serializado.

Se utiliza la personalización.
Interfaz de colección El nombre del contrato no está serializado. El nombre del contrato no está serializado.

No se utiliza la personalización.*
Colección no personalizada El nombre del contrato no está serializado. El nombre del contrato está serializado.

Se utiliza la personalización.**
Colección personalizada El nombre del contrato está serializado. No se utiliza la personalización.** El nombre del contrato está serializado.

Se utiliza la personalización del tipo asignado.**

*Con la clase NetDataContractSerializer , en este caso se usa la personalización. La clase NetDataContractSerializer también serializa el nombre de tipo real en este caso, por lo que la deserialización funciona como se espera.

**Estos casos generan instancias no válidas para el esquema y, por consiguiente, se deben evitar.

En los casos donde se serializa el nombre del contrato, el tipo de colección asignado debería estar en la lista de tipos conocidos. Lo contrario también es cierto: en los casos en los que no se serializa el nombre, no es necesario agregar el tipo a la lista de tipos conocidos.

Una matriz de un tipo derivado se puede asignar a una matriz de un tipo base. En este caso, el nombre del contrato para el tipo derivado se serializa para cada elemento repetido. Por ejemplo, si un tipo Book deriva del tipo LibraryItem, puede asignar una matriz de Book a una matriz de LibraryItem. Esto no se aplica a otros tipos de colección. Por ejemplo, puede asignar Generic List of Book a Generic List of LibraryItem. Sin embargo, puede asignar Generic List of LibraryItem que contiene las instancias Book . Tanto en el caso de matriz como de no matriz, Book debería estar en la lista de tipos conocidos.

Preservación de colecciones y de la referencia de objetos

Cuando un serializador funciona en un modo donde preserva las referencias de objetos, la preservación de la referencia de objetos también se aplica a las colecciones. Específicamente, la identidad de objeto se preserva tanto para colecciones completas como para elementos individuales contenidos en colecciones. Para los diccionarios, la identidad de objeto se preserva tanto para los objetos de par clave-valor como para los objetos individuales de clave y valor.

Consulte también