Types de collections dans les contrats de données

Une collection est une liste d'éléments d'un certain type. Dans .NET Framework, ces listes peuvent être représentées à l’aide de tableaux ou de divers autres types (liste générique, générique, BindingList<T>, StringCollection ou ArrayList). Par exemple, une collection peut contenir une liste d'adresses pour un client donné. Ces collections sont appelées collections liste, indépendamment de leur type réel.

Il existe une forme spéciale de collection qui représente une association entre un élément (la "clé") et un autre élément (la "valeur"). Dans .NET Framework, ces éléments sont représentés par des types tels que Hashtable et le dictionnaire générique. Par exemple, une collection association peut mapper une ville ("clé") à sa population ("valeur"). Ces collections sont appelées collections dictionnaire, indépendamment de leur type réel.

Les collections reçoivent un traitement spécial dans le modèle de contrat de données.

Les types qui implémentent l'interface IEnumerable , y compris des tableaux et des collections génériques, sont reconnus en tant que collections. Parmi eux, les types qui implémentent les interfaces IDictionary ou IDictionary<TKey,TValue> générique sont des collections dictionnaire ; tous les autres sont des collections liste.

Des spécifications supplémentaires relatives aux types de collections, telles qu’avoir une méthode appelée Add et un constructeur sans paramètre, sont présentées en détail dans les sections ci-dessous. Cela garantit que les types de collections peuvent être à la fois sérialisés et désérialisés. Cela signifie que certaines collections ne sont pas prises en charge directement, telles que ReadOnlyCollection<T> générique (car elle n’a aucun constructeur sans paramètre). Toutefois, pour plus d'informations sur la façon de contourner ces restrictions, consultez la section « Utilisation des types d'interfaces de collection et des collections en lecture seule » plus loin dans cette rubrique.

Les types contenus dans les collections doivent être des types de contrat de données ou, dans le cas contraire, ils doivent être sérialisables. Pour plus d’informations, consultez Types pris en charge par le sérialiseur de contrat de données.

Pour plus d’informations sur ce qui est et ce qui n’est pas considéré comme une collection valide, ainsi que sur la façon dont les collections sont sérialisées, consultez les informations relatives à la sérialisation des collections à la section « Règles de collection avancées » de cette rubrique.

Collections interchangeables

Toutes les collections liste du même type sont considérées comme ayant le même contrat de données (à moins d'être personnalisées à l'aide de l'attribut CollectionDataContractAttribute , comme cela est présenté ultérieurement dans cette rubrique). Ainsi, par exemple, les contrats de données suivants sont équivalents :

[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

Les deux contrats de données génèrent un XML semblable au code suivant :

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

L'interchangeabilité des collections vous permet d'utiliser, par exemple, un type de collection optimisé pour les performances sur le serveur, et un type de collection conçu pour être lié aux composants de l'interface utilisateur sur le client.

Semblables aux collections liste, toutes les collections dictionnaire qui ont les mêmes types de clé et de valeur sont considérées comme ayant le même contrat de données (à moins d'être personnalisées à l'aide de l'attribut CollectionDataContractAttribute ).

Seul le type de contrat de données importe en ce qui concerne l'équivalence de collections, pas les types .NET. Autrement dit, une collection de Type1 est considérée équivalente à une collection de Type2 si Type1 et Type2 ont des contrats de données équivalents.

Les collections non génériques sont considérées comme ayant le même contrat de données que les collections génériques de type Object. (Par exemple, les contrats de données pour ArrayList et la List<T> générique de type Object sont les mêmes.)

Utilisation des types d'interfaces de collection et des collections en lecture seule

Les types d'interfaces de collection (IEnumerable, IDictionary, IDictionary<TKey,TValue>générique, ou les interfaces dérivées de ces interfaces) sont également considérés comme ayant des contrats de données de collection équivalents aux contrats de données de collection pour les types de collections réels. Par conséquent, il est possible de déclarer le type en cours de sérialisation comme type d'interface de collection et les résultats sont les mêmes que si un type de collection réel était utilisé. Par exemple, les contrats de données suivants sont équivalents.

[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

Pendant la sérialisation, lorsque le type déclaré est une interface, le type d'instance réel utilisé peut être tout type qui implémente cette interface. Les restrictions présentées précédemment (avoir un constructeur sans paramètre et une méthode Add) ne s’appliquent pas. Par exemple, vous pouvez définir les adresses dans Customer2 sur une instance de ReadOnlyCollection<T> générique d'adresse, bien que vous ne puissiez pas déclarer directement un membre de données de type ReadOnlyCollection<T>générique.

Pendant la désérialisation, lorsque le type déclaré est une interface, le moteur de sérialisation choisit un type qui implémente l'interface déclarée, et ce type est instancié. Le mécanisme des types connus (décrit dans Data Contract Known Types) n’a aucun effet ici ; le choix du type est intégré dans WCF.

Personnalisation des types de collections

Vous pouvez personnaliser les types de collections à l'aide de l'attribut CollectionDataContractAttribute , qui présente plusieurs utilisations.

Notez que la personnalisation de types collection compromet l'interchangeabilité des collections. Il est donc généralement recommandé d'éviter d'appliquer cet attribut chaque fois que cela est possible. Pour plus d’informations sur ce problème, consultez la section « Règles de collection avancées », qui se trouve un peu plus loin dans cette rubrique.

Attribution de noms aux contrats de données de collection

Les règles d’attribution de noms aux types de collections sont semblables à celles d’attribution de noms aux types de contrat de données classiques, comme cela est décrit dans Data Contract Names, bien que certaines différences importantes existent :

  • L'attribut CollectionDataContractAttribute permet de personnaliser le nom, à la place de l'attribut DataContractAttribute . L'attribut CollectionDataContractAttribute possède également les propriétés Name et Namespace .

  • Lorsque l'attribut CollectionDataContractAttribute n'est pas appliqué, le nom et l'espace de noms par défaut pour les types de collections dépendent des noms et des espaces de noms des types inclus dans la collection. Ils ne sont pas affectés par le nom et l'espace de noms du type de collection lui-même. Pour obtenir un exemple, consultez les types suivants.

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

Le nom de contrat de données des deux types est "ArrayOfstring" et non pas "CustomerList1" ou "StringList1". Cela signifie que la sérialisation de n'importe lequel de ces types au niveau racine retourne un XML semblable au code suivant.

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

Cette règle d'attribution de noms a été choisie pour garantir que tous les types non personnalisés représentant une liste de chaînes aient les mêmes contrat de données et représentation XML. Cela permet l'interchangeabilité des collections. Dans cet exemple, CustomerList1 et StringList1 sont complètement interchangeables.

Toutefois, lorsque l'attribut CollectionDataContractAttribute est appliqué, la collection devient un contrat de données de collection personnalisé, même si aucune propriété n'est définie sur l'attribut. Le nom et l'espace de noms du contrat de données de collection dépendent alors du type de collection lui-même. Pour obtenir un exemple, consultez le type suivant.

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

En cas de sérialisation, le XML obtenu est semblable au code ci-dessous.

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

Vous remarquerez qu'il n'est plus équivalent à la représentation XML des types non personnalisés.

  • Vous pouvez utiliser les propriétés Name et Namespace pour personnaliser encore plus l'attribution de noms. Consultez la classe suivante.

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

Le XML obtenu est semblable au code suivant :

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

Pour plus d’informations, consultez la section « Règles de collection avancées », qui se trouve un peu plus loin dans cette rubrique.

Personnalisation du nom d'élément répétitif dans les collections liste

Les collections liste contiennent des entrées répétitives. Normalement, chaque entrée répétitive est représentée comme un élément nommé en fonction du nom de contrat de données du type contenu dans la collection.

Dans les exemples CustomerList , les collections contenaient des chaînes. Le nom de contrat de données pour le type primitif de chaîne est « string », donc l’élément répétitif était « <string> ».

Toutefois, le nom de cet élément répétitif peut être personnalisé à l'aide de la propriété ItemName sur l'attribut CollectionDataContractAttribute . Pour obtenir un exemple, consultez le type suivant.

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

Le XML obtenu est semblable au code suivant :

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

L'espace de noms de l'élément répétitif est toujours identique à celui du contrat de données de collection, qui peut être personnalisé à l'aide de la propriété Namespace , comme décrit précédemment.

Personnalisation des collections dictionnaire

Les collections dictionnaire sont essentiellement des listes d'entrées dans lesquelles chaque entrée a une clé suivie par une valeur. Comme avec les listes normales, vous pouvez modifier le nom d'élément qui correspond à l'élément répétitif en utilisant la propriété ItemName .

En outre, vous pouvez modifier les noms d'élément qui représentent la clé et la valeur en utilisant les propriétés KeyName et ValueName . Les espaces de noms pour ces éléments sont les mêmes que l'espace de noms du contrat de données de collection.

Pour obtenir un exemple, consultez le type suivant.

[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

En cas de sérialisation, le XML obtenu est semblable au code ci-dessous.

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

Pour plus d’informations sur les collections dictionnaire, consultez la section « Règles de collection avancées », qui se trouve un peu plus loin dans cette rubrique.

Collections et types connus

Vous n'avez pas besoin d'ajouter de types de collections aux types connus lorsqu'ils sont utilisés de manière polymorphe à la place d'autres collections ou interfaces de collection. Par exemple, si vous déclarez un membre de données de type IEnumerable et l'utilisez pour envoyer une instance de ArrayList, vous n'avez pas besoin d'ajouter ArrayList aux types connus.

Lorsque vous utilisez des collections de manière polymorphe au lieu de types qui ne sont pas des collections, elles doivent être ajoutées aux types connus. Par exemple, si vous déclarez un membre de données de type Object et l'utilisez pour envoyer une instance de ArrayList, ajoutez ArrayList aux types connus.

Cela ne vous permet pas de sérialiser toute collection équivalente de manière polymorphe. Par exemple, lorsque vous ajoutez ArrayList à la liste des types connus dans l'exemple précédent, cela ne vous permet pas d'assigner la classe Array of Object , bien qu'elle ait un contrat de données équivalent. Ce comportement n'est pas différent du comportement normal des types connus sur la sérialisation des types qui ne sont pas des collections, mais il est particulièrement important de le comprendre dans le cas des collections car celles-ci sont très souvent équivalentes.

Pendant la sérialisation, un seul type peut être connu dans toute portée donnée d'un contrat de données particulier et toutes les collections équivalentes ont les mêmes contrats de données. Cela signifie que, dans l'exemple précédent, vous ne pouvez pas ajouter à la fois ArrayList et Array of Object aux types connus au niveau de la même portée. Encore une fois, cela est équivalent au comportement des types connus pour les types qui ne sont pas des collections, mais cela est particulièrement important à comprendre pour les collections.

Les types connus peuvent également être requis pour le contenu des collections. Par exemple, si un ArrayList contient réellement des instances de Type1 et Type2, ces deux types doivent être ajoutés aux types connus.

L'exemple ci-dessous montre un graphique d'objet construit correctement à l'aide de collections et des types connus. Cet exemple est quelque peu inventé car, dans une application réelle, vous ne définiriez normalement pas les membres de données suivants comme Objectet, par conséquent, vous ne rencontreriez aucun problème de polymorphisme ou de type connu.

[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

Lors de la désérialisation, si le type déclaré est un type de collection, le type déclaré est instancié indépendamment du type qui a été réellement envoyé. Si le type déclaré est une interface de collection, le désérialiseur choisit un type à instancier sans se soucier des types connus.

De plus, lors de la désérialisation, si le type déclaré n'est pas un type de collection mais un type de collection est envoyé, un type de collection correspondant est choisi dans la liste des types connus. Il est possible d'ajouter des types d'interfaces de collection à la liste des types connus lors de la désérialisation. Dans ce cas, le moteur de désérialisation choisit de nouveau un type à instancier.

Collections et classe NetDataContractSerializer

Lorsque la classe NetDataContractSerializer est utilisée, les types de collections non personnalisés (sans l'attribut CollectionDataContractAttribute ) qui ne sont pas des tableaux perdent leur signification spéciale.

Les types de collections non personnalisés marqués avec l'attribut SerializableAttribute peuvent encore être sérialisés par la classe NetDataContractSerializer en fonction de l'attribut SerializableAttribute ou des règles d'interface ISerializable .

Les types de collections personnalisés, les interfaces de collection et les tableaux sont encore traités comme des collections, même lorsque la classe NetDataContractSerializer est utilisée.

Collections et schéma

Toutes les collections équivalentes ont la même représentation dans le schéma XSD (XML Schema Definition). C'est pourquoi vous n'obtiendrez normalement pas le même type de collection dans le code client généré que sur le serveur. Par exemple, le serveur peut utiliser un contrat de données avec un membre de données List<T> générique d'éléments Integer, alors que, dans le code client généré, le même membre de données peut devenir un tableau d'entiers.

Les collections dictionnaire sont marquées avec une annotation de schéma spécifique à WCF qui indique qu’il s’agit de dictionnaires ; dans le cas contraire, il ne serait pas possible de les distinguer de listes ordinaires contenant des entrées composées d’une clé et d’une valeur. Pour une description exacte de la façon dont les collections sont représentées dans le schéma de contrat de données, consultez Data Contract Schema Reference.

Par défaut, les types ne sont pas générés pour les collections non personnalisées dans le code importé. Les membres de données des types de collections liste sont importés en tant que tableaux, alors que les membres de données des types de collections dictionnaire sont importés en tant que dictionnaire générique.

Toutefois, pour les collections personnalisées, des types distincts sont générés, marqués avec l'attribut CollectionDataContractAttribute . (Un type de collection personnalisée dans le schéma est un type qui n’utilise pas l’espace de noms, le nom, le nom d’élément répétitif ou les noms des éléments clé/valeur par défaut.) Ces types sont des types vides qui dérivent de la List<T> générique pour les types liste et du dictionnaire générique pour les types dictionnaire.

Par exemple, vous pouvez avoir les types ci-dessous sur le serveur.

[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

Lorsque le schéma est exporté puis importé de nouveau, le code client généré est semblable à ce qui suit (les propriétés sont remplacées par des champs pour une meilleure lisibilité).

[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

Vous pouvez utiliser des types différents dans le code généré à la place des types par défaut. Par exemple, vous pouvez utiliser le BindingList<T> générique à la place de tableaux normaux pour vos membres de données pour pouvoir les lier plus facilement aux composants d'interface utilisateur.

Pour choisir des types de collections à générer, passez une liste de types de collections que vous souhaitez utiliser dans la propriété ReferencedCollectionTypes de l'objet ImportOptions lors de l'importation du schéma. Ces types sont appelés types de collections référencés.

Lorsque des types génériques sont référencés, il doit s'agir de génériques complètement ouverts ou complètement fermés.

Notes

Lorsque vous utilisez l’outil Svcutil.exe, cette référence peut être réalisée à l’aide du commutateur de ligne de commande /collectionType (forme abrégée : /ct). N’oubliez pas que vous devez également spécifier l’assembly pour les types de collections référencés à l’aide du commutateur /reference (forme abrégée : /r). Si le type est générique, il doit être suivi par une apostrophe inverse et le nombre de paramètres génériques. L'apostrophe inverse (`) ne doit pas être confondue avec le caractère guillemet simple (‘). Vous pouvez spécifier plusieurs types de collections référencés en utilisant plusieurs fois le commutateur /collectionType .

Par exemple, pour provoquer l'importation de toutes les listes en tant que List<T>générique.

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

Lors de l'importation d'une collection quelconque, cette liste de types de collections référencés est analysée et la collection qui correspond le mieux est utilisée, le cas échéant, soit comme type de membre de données (pour les collections non personnalisées), soit comme type de base à partir duquel dériver (pour les collections personnalisées). Les dictionnaires sont comparés uniquement à des dictionnaires, tandis que les listes sont comparées à des listes.

Par exemple, si vous ajoutez le BindingList<T> générique et Hashtable à la liste des types référencés, le code client généré pour l'exemple précédent sera semblable au code ci-dessous.

[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

Vous pouvez spécifier des types d'interfaces de collection dans le cadre de vos types de collections référencés, mais vous ne pouvez pas spécifier de types de collections non valides (tels que des types sans méthode Add ou sans constructeur public).

Un générique fermé est considéré comme la solution la mieux adaptée. (Les types non génériques sont considérés équivalents aux génériques fermés d' Object.) Par exemple, si les types List<T> générique de DateTime, BindingList<T> générique (générique ouvert) et ArrayList sont les types de collections référencés, le code ci-dessous est généré.

[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

Pour les collections liste, seuls les cas indiqués dans le tableau suivant sont pris en charge.

Type référencé Interface implémentée par le type référencé Exemple Type traité comme :
Non générique ou générique fermé (nombre de paramètres quelconque) Non générique MyType : IList

or

MyType<T> : IList

où T= int
Générique fermé d' Object (par exemple, IList<object>)
Non générique ou générique fermé (nombre quelconque de paramètres qui ne correspondent pas nécessairement au type de collection) Générique fermé MyType : IList<string>

or

MyType<T> : IList<string> où T=int
Générique fermé (par exemple, IList<string>)
Générique fermé avec un nombre quelconque de paramètres Générique ouvert utilisant un des paramètres du type MyType<T,U,V> : IList<U>

où T=int, U=string, V=bool
Générique fermé (par exemple, IList<string>)
Générique ouvert avec un paramètre Générique ouvert utilisant le paramètre du type MyType<T> : IList<T>, T est ouvert Générique ouvert (par exemple, IList<T>)

Si un type implémente plusieurs interfaces de collection liste, les restrictions suivantes s'appliquent :

  • Si le type implémente le IEnumerable<T> générique (ou ses interfaces dérivées) plusieurs fois pour des types différents, le type n'est pas considéré comme un type de collection référencé valide et il est ignoré. Cela est vrai même si certaines implémentations ne sont pas valides ou utilisent des génériques ouverts. Par exemple, un type qui implémente le IEnumerable<T> générique d' int et le IEnumerable<T> générique de T ne serait jamais utilisé comme collection référencée d' int ou tout autre type, que le type ait ou non une méthode Add qui accepte int ou une méthode Add qui accepte un paramètre de type T, ou les deux.

  • Si le type implémente une interface de collection générique ainsi que IList, le type ne sera jamais utilisé comme type de collection référencé à moins que l'interface de collection générique ne soit un générique fermé de type Object.

Pour les collections dictionnaire, seuls les cas indiqués dans le tableau suivant sont pris en charge.

Type référencé Interface implémentée par le type référencé Exemple Type traité comme
Non générique ou générique fermé (nombre de paramètres quelconque) IDictionary MyType : IDictionary

or

MyType<T> : IDictionary où T=int
Générique fermé IDictionary<object,object>
Générique fermé (nombre quelconque de paramètres) IDictionary<TKey,TValue>, fermé MyType<T> : IDictionary<string, bool> où T=int Générique fermé (par exemple, IDictionary<string,bool>)
Générique fermé (nombre quelconque de paramètres) IDictionary<TKey,TValue>générique, la clé ou la valeur est fermée, l'autre est ouverte et utilise un des paramètres du type MyType<T,U,V> : IDictionary<string,V> où T=int, U=float, V=bool

or

MyType<Z> : IDictionary<Z,bool> où Z=string
Générique fermé (par exemple, IDictionary<string,bool>)
Générique fermé (nombre quelconque de paramètres) IDictionary<TKey,TValue>générique, la clé et la valeur sont ouvertes et chacune d'elles utilise un des paramètres du type MyType<T,U,V> : IDictionary<V,U> où T=int, U=bool, V=string Générique fermé (par exemple, IDictionary<string,bool>)
Générique ouvert (deux paramètres) IDictionary<TKey,TValue>générique, ouvert, utilise les deux paramètres génériques du type dans l'ordre où ils apparaissent MyType<K,V> : IDictionary<K,V>, K et V sont ouverts tous les deux Générique ouvert (par exemple, IDictionary<K,V>)

Si le type implémente IDictionary et le IDictionary<TKey,TValue>générique, seul le IDictionary<TKey,TValue> générique est pris en compte.

Le référencement de types génériques partiels n'est pas pris en charge.

Les doublons ne sont pas autorisés. Par exemple, vous ne pouvez pas ajouter à la fois la List<T> générique d'éléments Integer et la collection générique d'éléments Integer à ReferencedCollectionTypes, car cela empêche de déterminer laquelle utiliser si une liste d'entiers est détectée dans le schéma. Les doublons sont détectés uniquement si un type présent dans le schéma expose le problème des doublons. Par exemple, si le schéma en cours d'importation ne contient pas de listes d'entiers, il est autorisé d'avoir à la fois la List<T> générique d'éléments Integer et la collection générique d'éléments Integer dans ReferencedCollectionTypes, mais aucune d'elles n'a un quelconque effet.

Règles de collection avancées

Sérialisation de collections

La liste suivante répertorie les règles de collection pour la sérialisation :

  • L'association de types de collections (avoir des collections de collections) est autorisée. Les tableaux en escalier sont traités comme des collections de collections. Les tableaux multidimensionnels ne sont pas pris en charge.

  • Les tableaux d'octets et les tableaux de XmlNode sont des types de tableau spéciaux qui sont traités comme des primitifs, pas comme des collections. La sérialisation d'un tableau d'octets génère un élément XML unique qui contient un segment de données encodées en Base64, au lieu d'un élément distinct pour chaque octet. Pour plus d’informations sur la façon dont un tableau de XmlNode est traité, consultez Types XML et ADO.NET dans les contrats de données. Naturellement, ces types spéciaux peuvent eux-mêmes participer aux collections : un tableau de tableaux d'octets génère plusieurs éléments XML contenant chacun un segment de données encodées en Base64.

  • Si l'attribut DataContractAttribute est appliqué à un type de collection, le type est traité comme un type de contrat de données standard, pas comme une collection.

  • Si un type de collection implémente l'interface IXmlSerializable , les restrictions suivantes s'appliquent, s'il s'agit d'un type myType:IList<string>, IXmlSerializable:

    • Si le type déclaré est IList<string>, le type est sérialisé en tant que liste.

    • Si le type déclaré est myType, il est sérialisé en tant que IXmlSerializable.

    • Si le type déclaré est IXmlSerializable, il est sérialisé en tant que IXmlSerializable, mais uniquement si vous ajoutez myType à la liste des types connus.

  • Les collections sont sérialisées et désérialisées à l'aide des méthodes indiquées dans le tableau ci-dessous.

Le type de collection implémente Méthodes appelées lors de la sérialisation Méthodes appelées lors de la désérialisation
Generic IDictionary<TKey,TValue> get_Keys, get_Values Generic Add
IDictionary get_Keys, get_Values Add
Generic IList<T> Indexeur IList<T> générique Generic Add
Generic ICollection<T> Énumérateur Generic Add
IList IndexeurIList Add
Generic IEnumerable<T> GetEnumerator Méthode non statique nommée Add qui accepte un paramètre du type approprié (le type du paramètre générique ou un de ses types de base). Une telle méthode doit exister pour que le sérialiseur traite un type de collection comme une collection lors de la sérialisation et de la désérialisation.
IEnumerable (et donc ICollection, qui en dérive) GetEnumerator Méthode non statique nommée Add qui accepte un paramètre de type Object. Une telle méthode doit exister pour que le sérialiseur traite un type de collection comme une collection lors de la sérialisation et de la désérialisation.

Le tableau précédent répertorie les interfaces de collection dans l'ordre de priorité décroissant. Cela signifie, par exemple, que si un type implémente à la fois IList et IEnumerable<T>générique, la collection est sérialisée et désérialisée en fonction des règles IList :

  • Lors de la désérialisation, toutes les collections sont désérialisées en créant en premier lieu une instance du type en appelant le constructeur sans paramètre, qui doit être présent pour que le sérialiseur traite un type de collection comme une collection lors de la sérialisation et de la désérialisation.

  • Si la même interface de collection générique est implémentée plusieurs fois (par exemple, si un type implémente à la fois le ICollection<T> générique d'éléments Integer et le ICollection<T> générique d'éléments String) et qu'aucune interface de priorité supérieure n'est trouvée, la collection n'est pas traitée comme une collection valide.

  • L'attribut SerializableAttribute peut être appliqué aux types de collections et ces derniers peuvent implémenter l'interface ISerializable . Ces deux éléments sont ignorés. Toutefois, si le type ne satisfait pas pleinement les spécifications de type de collection (par exemple, si la méthode Add est manquante), le type n'est pas considéré comme un type de collection, et par conséquent l'attribut SerializableAttribute et l'interface ISerializable sont utilisés pour déterminer si le type peut être sérialisé.

  • L'application de l'attribut CollectionDataContractAttribute à une collection pour la personnaliser supprime le mécanisme de secours SerializableAttribute mentionné précédemment. À la place, si une collection personnalisée ne satisfait pas les spécifications de type de collection, une exception InvalidDataContractException est levée. La chaîne d’exception contient souvent des informations qui expliquent pourquoi un type donné n’est pas considéré comme une collection valide (aucune méthode Add, aucun constructeur sans paramètre, etc.), si bien qu’il est souvent utile d’appliquer l’attribut CollectionDataContractAttribute à des fins de débogage.

Attribution des noms de collections

La liste suivante répertorie les règles d'attribution des noms des collections :

  • L’espace de noms par défaut pour tous les contrats de données de collection dictionnaire, ainsi que pour les contrats de données de collection liste qui contiennent des types primitifs, est http://schemas.microsoft.com/2003/10/Serialization/Arrays à moins qu’il ait été remplacé à l’aide de Namespace. Les types qui correspondent aux types XSD intégrés, ainsi que les types char, Timespanet Guid , sont considérés comme des primitifs à cette fin.

  • L'espace de noms par défaut des types de collections contenant des types non primitifs, à moins qu'il soit remplacé à l'aide de Namespace, est le même que celui des noms de contrat de données du type inclus dans la collection.

  • Le nom par défaut pour les contrats de données de collection de liste, à moins qu'il ne soit remplacé à l'aide de Name, est la chaîne "ArrayOf" associée au nom du contrat de données du type inclus dans la collection. Par exemple, le nom du contrat de données pour une liste générique d'éléments Integer est "ArrayOfint". N'oubliez pas que le nom de contrat de données d' Object est "anyType", afin que le nom de contrat de données de listes non génériques comme ArrayList soit "ArrayOfanyType".

Le nom par défaut pour les contrats de données de collection dictionnaire, à moins qu'il soit remplacé à l'aide de Name, est la chaîne "ArrayOfKeyValueOf" associée au nom du contrat de données du type de clé suivie par le nom du contrat de données du type de valeur. Par exemple, le nom du contrat de données pour un dictionnaire générique d'éléments String et Integer est "ArrayOfKeyValueOfstringint". En outre, si les types de clé ou de valeur ne sont pas des types primitifs, un hachage d'espace de noms des espaces de noms de contrat de données des types de clé et de valeur est ajouté au nom. Pour plus d’informations sur les codes de hachage d’espace de noms, consultez Noms de contrats de données.

Chaque contrat de données de collection dictionnaire possède un contrat de données auxiliaire qui représente une entrée dans le dictionnaire. Son nom est le même que pour le contrat de données de dictionnaire, à l'exception du préfixe "ArrayOf", et son espace de noms est le même que pour le contrat de données de dictionnaire. Par exemple, pour le contrat de données de dictionnaire "ArrayOfKeyValueOfstringint", le contrat de données "KeyValueofstringint" représente une entrée dans le dictionnaire. Vous pouvez personnaliser le nom de ce contrat de données en utilisant la propriété ItemName tel que cela est décrit dans la section suivante.

Les règles d’attribution de noms de type générique, comme décrit dans Data Contract Names, s’appliquent pleinement aux types de collections ; en d’autres termes, vous pouvez utiliser des accolades dans Name pour indiquer des paramètres de type générique. Toutefois, notez que les nombres indiqués entre les accolades font référence aux paramètres génériques et non aux types inclus dans la collection.

Personnalisation des collections

Les utilisations suivantes de l'attribut CollectionDataContractAttribute sont interdites et entraînent une exception InvalidDataContractException :

Règles de polymorphisme

Comme cela a été mentionné précédemment, la personnalisation de collections à l'aide de l'attribut CollectionDataContractAttribute peut interférer avec l'interchangeabilité des collections. Deux types de collections personnalisés peuvent être considérés équivalents uniquement si leurs noms, leurs espaces de noms, leurs noms d'élément, ainsi que leurs noms de clé et de valeur (si ce sont des collections dictionnaire) correspondent.

En raison des personnalisations, il est possible d'utiliser par inadvertance un contrat de données de collection lorsqu'un autre contrat est attendu. Cela doit être évité. Consultez les types suivants.

[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

Dans ce cas, une instance de Marks1 peut être assignée à testMarks. Toutefois, Marks2 ne doit pas être utilisé car son contrat de données n'est pas considéré équivalent au contrat de données IList<int> . Le nom de contrat de données est « Marks2 » et non pas « ArrayOfint », et le nom d’élément répétitif est « <mark> » et non pas « <int> ».

Les règles indiquées dans le tableau ci-dessous s'appliquent à l'assignation polymorphe de collections.

Type déclaré Assignation d'une collection non personnalisée Assignation d'une collection personnalisée
Object Le nom de contrat est sérialisé. Le nom de contrat est sérialisé.

La personnalisation est utilisée.
Interface de collection Le nom de contrat n'est pas sérialisé. Le nom de contrat n'est pas sérialisé.

La personnalisation n'est pas utilisée.*
Collection non personnalisée Le nom de contrat n'est pas sérialisé. Le nom de contrat est sérialisé.

La personnalisation est utilisée.**
Collection personnalisée Le nom de contrat est sérialisé. La personnalisation n'est pas utilisée.** Le nom de contrat est sérialisé.

La personnalisation du type assigné est utilisée.**

* Avec la classe NetDataContractSerializer , la personnalisation est utilisée dans ce cas. La classe NetDataContractSerializer sérialise également le nom de type réel dans ce cas, si bien que la désérialisation fonctionne comme prévu.

** De telles situations génèrent des instances de schéma non valides et doivent donc être évitées.

Dans les cas où le nom de contrat est sérialisé, le type de collection assigné doit figurer dans la liste des types connus. Le contraire est également vrai : dans les cas où le nom n'est pas sérialisé, l'ajout du type dans la liste des types connus n'est pas nécessaire.

Un tableau d'un type dérivé peut être assigné à un tableau d'un type de base. Dans ce cas, le nom de contrat pour le type dérivé est sérialisé pour chaque élément répétitif. Par exemple, si un type Book dérive du type LibraryItem, vous pouvez assigner un tableau d'éléments Book à un tableau d'éléments LibraryItem. Cela ne s'applique pas aux autres types de collections. Par exemple, vous ne pouvez pas assigner une Generic List of Book à une Generic List of LibraryItem. Toutefois, vous pouvez assigner une Generic List of LibraryItem qui contient des instances Book . Dans les deux cas avec tableau et sans tableau, Book doit figurer dans la liste des types connus.

Collections et conservation des références aux objets

Lorsqu'un sérialiseur fonctionne dans un mode où il conserve les références aux objets, la conservation des références aux objets s'applique également aux collections. Spécifiquement, l'identité des objets est conservée à la fois pour les collections entières et pour les éléments individuels inclus dans les collections. Pour les dictionnaires, l'identité des objets est conservée à la fois pour les objets paire clé/valeur et pour les objets clé et valeur individuels.

Voir aussi