Partager via


Types de collecte 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 d’un large éventail d’autres types (Liste générique, Générique BindingList<T>, StringCollectionou ArrayList). Par exemple, une collection peut contenir une liste d’adresses pour un client donné. Ces collections sont appelées collections de listes, quel que soit leur type réel.

Une forme spéciale de collection existe qui représente une association entre un élément (la « clé ») et une autre (la « valeur »). Dans le .NET Framework, ceux-ci sont représentés par des types tels que Hashtable et le dictionnaire générique. Par exemple, une collection associative peut associer une ville (“clé”) à sa population (“valeur”). Ces collections sont appelées collections de dictionnaires, quel que soit 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 les tableaux et les collections génériques, sont reconnus comme des collections. Parmi ceux-ci, les types qui implémentent les interfaces IDictionary ou génériques IDictionary<TKey,TValue> sont des collections de dictionnaire ; tous les autres sont des collections de liste.

Des exigences supplémentaires sur les types de collection, telles qu’une méthode appelée Add et un constructeur sans paramètre, sont décrites en détail dans les sections suivantes. Cela garantit que les types de collection peuvent être sérialisés et désérialisés. Cela signifie que certaines collections ne sont pas directement prises en charge, telles que le générique ReadOnlyCollection<T> (car il n’a pas de constructeur sans paramètre). Toutefois, pour plus d’informations sur le contournement de ces restrictions, consultez la section « Using Collection Interface Types and Read-Only Collections » plus loin dans cette rubrique.

Les types contenus dans les collections doivent être des types de contrat de données ou ê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 regroupements sont sérialisés, consultez les informations sur la sérialisation des collections dans la section « Règles avancées de collection » de cette rubrique.

Collections interchangeables

Toutes les collections de listes du même type sont considérées comme ayant le même contrat de données (sauf si elles sont personnalisées à l’aide de l’attribut CollectionDataContractAttribute , comme indiqué plus loin dans cette rubrique). 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 entraînent des données XML similaires au code suivant.

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

L’échange de collection 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.

À l’instar des collections de listes, toutes les collections de dictionnaire qui ont les mêmes types clé et valeur sont considérées comme ayant le même contrat de données (sauf si elles sont personnalisées par l’attribut CollectionDataContractAttribute ).

Seul le type de contrat de données est important en ce qui concerne l’équivalence de collection, pas les types .NET. Autrement dit, une collection de Type1 est considérée comme é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 Générique List<T> de Object sont les mêmes.)

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

Les types d’interface de collecte (IEnumerable, IDictionarygénériques IDictionary<TKey,TValue>ou interfaces dérivées de ces interfaces) sont également considérés comme ayant des contrats de données de collecte, équivalents aux contrats de données de collecte pour les types de collecte réels. Par conséquent, il est possible de déclarer le type sérialisé en tant que type d’interface de collection et les résultats sont identiques si un type de collection réel avait été 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

Lors de la sérialisation, lorsque le type déclaré est une interface, le type d’instance réel utilisé peut être n’importe quel type qui implémente cette interface. Les restrictions décrites précédemment (avoir un constructeur sans paramètre et une Add méthode) ne s’appliquent pas. Par exemple, vous pouvez définir des adresses dans Customer2 sur une instance de Generic ReadOnlyCollection<T> of Address, même si vous ne pouvez pas déclarer directement un membre de données de type Générique ReadOnlyCollection<T>.

Lors de 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 le type est instancié. Le mécanisme de types connus (décrit dans Les types connus de contrat de données) n’a aucun effet ici ; le choix du type est intégré à WCF.

Personnalisation des types de collection

Vous pouvez personnaliser les types de collection à l’aide de l’attribut CollectionDataContractAttribute , qui a plusieurs utilisations.

Notez que la personnalisation des types de collection compromet l’échange de collection. Il est donc généralement recommandé d’éviter d’appliquer cet attribut dans la mesure du possible. Pour plus d’informations sur ce problème, consultez la section « Règles de collection avancées » plus loin dans cette rubrique.

Attribution de noms aux contrats de données de collection

Les règles de nomination des types de collection sont similaires à celles pour nommer les types de contrats de données réguliers, comme décrit dans Les noms de contrats de données, bien que certaines différences importantes existent.

  • L’attribut CollectionDataContractAttribute est utilisé pour personnaliser le nom, au lieu 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 collection dépendent des noms et espaces de noms des types contenus dans la collection. Ils ne sont pas affectés par le nom du type de collection ni par son espace de noms. Pour obtenir un exemple, consultez les types suivants.

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

Le nom du contrat de données des deux types est « ArrayOfstring » et non « CustomerList1 » ou « StringList1 ». Cela signifie que la sérialisation d’un de ces types au niveau racine génère du code XML similaire au code suivant.

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

Cette règle de nommage a été choisie pour s’assurer que tout type non personnalisé qui représente une liste de chaînes a le même contrat de données et la représentation XML. Cela rend possible 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 collecte 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 collecte dépendent ensuite 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 code XML résultant est similaire à ce qui suit.

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

Notez que cela 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 davantage le nommage. 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 code XML obtenu est similaire à ce qui suit.

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

Pour plus d’informations, consultez la section « Règles de collecte avancées » plus loin dans cette rubrique.

Personnalisation du nom d’élément répétitif dans les collections de listes

Les collections de listes contiennent des entrées répétées. Normalement, chaque entrée répétée est représentée en tant qu’élément nommé en fonction du nom du contrat de données du type contenu dans la collection.

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

Toutefois, à l’aide de la ItemName propriété sur l’attribut CollectionDataContractAttribute , ce nom d’élément répétitif peut être personnalisé. 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 code XML obtenu est similaire à ce qui suit.

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

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

Personnalisation des collections de dictionnaires

Les collections de dictionnaires sont essentiellement des listes d’entrées, où chaque entrée a une clé suivie d’une valeur. Tout comme avec les listes régulières, vous pouvez modifier le nom de l’élément qui correspond à l’élément répétitif à l’aide de la ItemName propriété.

En outre, vous pouvez modifier les noms des éléments qui représentent la clé et la valeur à l'aide des propriétés KeyName et ValueName. Les espaces de noms de ces éléments sont identiques à l’espace de noms du contrat de données de collecte.

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 code XML résultant est similaire à ce qui suit.

<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 de dictionnaires, consultez la section « Règles de collection avancées » plus loin dans cette rubrique.

Collections et types connus

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

Lorsque vous utilisez des collections polymorphes à la place de types non-collection, elles doivent être ajoutées aux types connus. Par exemple, si vous déclarez un membre de données de type Object et que vous l'utilisez pour envoyer une instance de ArrayList, ajoutez ArrayList aux types connus.

Cela ne vous permet pas de sérialiser une collection équivalente polymorphiquement. Par exemple, lorsque vous ajoutez ArrayList à la liste des types connus dans l’exemple précédent, cela ne vous permet pas d’affecter la Array of Object classe, même si elle a un contrat de données équivalent. Cela n'est pas différent du comportement des types connus ordinaires concernant la sérialisation des types non-collection, mais il est particulièrement important de comprendre cela dans le cas des collections, car il est très fréquent que des collections soient équivalentes.

Lors de la sérialisation, un seul type peut être connu dans n’importe quelle étendue donnée pour un contrat de données donné, et les collections équivalentes ont tous 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 dans la même étendue. 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 de Type2, ces deux types doivent être ajoutés aux types connus.

L’exemple suivant montre un graphique d’objet construit correctement à l’aide de collections et de types connus. L’exemple est quelque peu contrif, car dans une application réelle, vous ne définissez normalement pas les membres de données suivants comme Object, et n’avez donc pas de problèmes connus de type/polymorphisme.

[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 sélectionne un type à instancier sans tenir compte des types connus.

En outre, lors de la désérialisation, si le type déclaré n’est pas un type de collection, mais qu’un type de collection est envoyé, un type de collection correspondant est sélectionné dans la liste des types connus. Il est possible d’ajouter des types d’interface 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 NetDataContractSerializer classe est en cours d’utilisation, les types de collection non personnalisés (sans l’attribut CollectionDataContractAttribute ) qui ne sont pas des tableaux perdent leur signification spéciale.

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

Les types de collection personnalisés, les interfaces de collection et les tableaux sont toujours traités comme des collections, même lorsque la NetDataContractSerializer classe est en cours d’utilisation.

Collections et schéma

Toutes les collections équivalentes ont la même représentation dans le schéma XSD (XML Schema Definition Language). En raison de cela, vous n’obtenez normalement pas le même type de collection dans le code client généré que celui sur le serveur. Par exemple, le serveur peut utiliser un contrat de données avec un List<T> membre générique de données Integer, mais dans le code client généré, le même membre de données peut devenir un tableau d’entiers.

Les collections de dictionnaires sont marquées d'une annotation de schéma spécifique à WCF qui indiquent qu'il s’agit de dictionnaires ; sinon, elles sont indistinguables de listes simples qui contiennent des entrées avec une clé et une valeur. Pour obtenir une description exacte de la façon dont les collections sont représentées dans le schéma de contrat de données, consultez Informations de référence sur le schéma du contrat de données.

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é dans le schéma est un type qui n’utilise pas l’espace de noms par défaut, le nom par défaut, le nom d’élément répétitif par défaut, ou les noms d’éléments clé/valeur par défaut.) Ces types sont des types vides qui dérivent de Generic List<T> pour les types de liste et de le Dictionnaire Générique pour les types de dictionnaire.

Par exemple, vous pouvez avoir les types suivants 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é et importé à nouveau, le code client généré est similaire à ce qui suit (les champs sont affichés au lieu de propriétés pour faciliter la lecture).

[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 souhaiterez peut-être utiliser différents types dans le code généré que ceux par défaut. Par exemple, vous pouvez utiliser Generic BindingList<T> au lieu de tableaux standard pour vos membres de données afin de faciliter leur liaison aux composants de l’interface utilisateur.

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

Lorsque des types génériques sont référencés, ils doivent être des génériques entièrement ouverts ou des génériques entièrement fermés.

Remarque

Lorsque vous utilisez l’outil Svcutil.exe, cette référence peut être effectué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 collection 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 d’un guillemet précédent et du nombre de paramètres génériques. Le guillemet précédent (') ne doit pas être confondu avec le caractère guillemet unique ('). Vous pouvez spécifier plusieurs types de collection référencés à l’aide du commutateur /collectionType plusieurs fois.

Par exemple, pour que toutes les listes soient importées en tant que génériques List<T>.

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, cette liste de types de collection référencés est analysée et la collection la plus adaptée est utilisée si une collection est trouvée, soit en tant que type de membre de données (pour les collections non personnalisées) soit en tant que type de base à dériver (pour les collections personnalisées). Les dictionnaires sont mis en correspondance uniquement par rapport aux dictionnaires, tandis que les listes sont mises en correspondance avec les listes.

Par exemple, si vous ajoutez le générique BindingList<T> et Hashtable à la liste des types référencés, le code client généré pour l’exemple précédent est similaire à ce qui suit.

[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’interface de collection dans le cadre de vos types de collection référencés, mais vous ne pouvez pas spécifier de types de collection non valides (comme ceux Add sans méthode ni 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 comme équivalents aux génériques fermés de Object). Par exemple, si les types Generic List<T> of DateTime, Generic BindingList<T> (open generic) et ArrayList sont les types de collection référencés, les éléments suivants sont générés.

[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 regroupements de listes, seuls les cas du tableau suivant sont pris en charge.

Type référencé Interface implémentée par type référencé Exemple : Type traité comme :
Générique non-standard ou générique fermé (n'importe quel nombre de paramètres) Non générique MyType : IList

ou

MyType<T> : IList

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

ou

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 Ouvrir un générique en utilisant l’un des paramètres du type MyType<T,U,V> : IList<U>

where T=int, U=string, V=bool
Générique fermé (par exemple, IList<string>)
Ouvrir générique 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 de listes, les restrictions suivantes s’appliquent :

  • Si le type implémente plusieurs fois le générique IEnumerable<T> (ou ses interfaces dérivées) pour différents types, il n’est pas considéré comme un type de collection référencé valide et 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 Generic IEnumerable<T> de int et Generic IEnumerable<T> de T ne serait jamais utilisé comme une collection référencée de int ou de tout autre type, que le type ait une méthode Add acceptant int ou une méthode Add acceptant 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 n’est jamais utilisé comme type de collection référencé, sauf si l’interface de collection générique est un générique fermé de type Object.

Pour les collections de dictionnaires, seuls les cas du tableau suivant sont pris en charge.

Type référencé Interface implémentée par type référencé Exemple : Type traité comme
Générique non-standard ou générique fermé (n'importe quel nombre de paramètres) IDictionary MyType : IDictionary

ou

MyType<T> : IDictionary où T=int
Générique fermé IDictionary<object,object>
Générique fermé (n’importe quel nombre 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é (n’importe quel nombre de paramètres) Générique IDictionary<TKey,TValue>, l’une des clés ou de la valeur est fermée, l’autre est ouverte et utilise l’un des paramètres du type MyType<T,U,V> : IDictionary<string,V> where T=int, U=float,V=bool

ou

MyType<Z> : IDictionary<Z,bool> où Z=string
Générique fermé (par exemple, IDictionary<string,bool>)
Générique fermé (n’importe quel nombre de paramètres) Générique IDictionary<TKey,TValue>, clé et valeur sont ouverts et chacun utilise l’un des paramètres du type MyType<T,U,V> : IDictionary<V,U> where T=int, U=bool, V=string Générique fermé (par exemple, IDictionary<string,bool>)
Ouvrir un type générique avec deux paramètres Générique IDictionary<TKey,TValue>, ouvert, utilise les deux paramètres génériques du type dans l’ordre dans lequel ils apparaissent MyType<K,V> : IDictionary<K,V>, K et V sont tous les deux ouverts Générique ouvert (par exemple, IDictionary<K,V>)

Si le type implémente à la fois IDictionary et Générique IDictionary<TKey,TValue>, seul le générique IDictionary<TKey,TValue> 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 le générique List<T> de Integer et la collection générique de Integer à ReferencedCollectionTypes, car cela rend impossible de déterminer lequel utiliser lorsqu'une liste d'entiers est trouvée dans le schéma. Les doublons sont détectés uniquement s’il existe un type dans le schéma qui 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 possible d'avoir à la fois le Générique List<T> de Integer et la collection générique de Integer dans le ReferencedCollectionTypes, mais aucun n'a d'effet.

Règles de collecte avancées

Sérialisation des collections

Voici une liste de règles de collecte 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 tableaux spéciaux qui sont traités comme des primitives, et non comme des collections. La sérialisation d’un tableau d’octets entraîne un seul élément XML qui contient un bloc de données encodées en Base64, au lieu d’un élément distinct pour chaque octet. Pour plus d'informations sur le traitement d'un tableau de XmlNode, consultez XML et les types ADO.NET dans les contrats de données. Bien sûr, ces types spéciaux peuvent eux-mêmes participer à des collections : un tableau d’octets entraîne plusieurs éléments XML, chacun contenant un bloc de données codé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, et non comme une collection.

  • Si un type de collection implémente l’interface IXmlSerializable , les règles suivantes s’appliquent, en fonction 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 suivant.

Le type de collection implémente Méthode(s) appelée(s) lors de la sérialisation Méthodes appelées lors de la désérialisation
Générique IDictionary<TKey,TValue> get_Keys, get_Values Ajout générique
IDictionary get_Keys, get_Values Add
Générique IList<T> Indexeur générique IList<T> Ajout générique
Générique ICollection<T> Énumérateur Ajout générique
IList IList Indexeur Add
Générique IEnumerable<T> GetEnumerator Méthode non statique appelée Add qui accepte un paramètre du type approprié (le type du paramètre générique ou l’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 pendant la sérialisation et la désérialisation.
IEnumerable (et donc ICollection, qui dérive de celui-ci) GetEnumerator Méthode non statique appelé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 pendant la sérialisation et la désérialisation.

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

  • Lors de la désérialisation, toutes les collections sont désérialisées en créant d’abord 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 pendant la sérialisation et 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 générique ICollection<T> et Integer générique ICollection<T> de String) et qu’aucune interface de priorité supérieure n’est trouvée, la collection n’est pas traitée comme une collection valide.

  • Les types de collection peuvent avoir l’attribut SerializableAttribute appliqué à eux et peuvent implémenter l’interface ISerializable . Ces deux éléments sont ignorés. Toutefois, si le type ne répond pas entièrement aux exigences de type de collection (par exemple, la Add méthode 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 SerializableAttribute mécanisme de secours précédent. À 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 expliquant pourquoi un type donné n’est pas considéré comme une collection valide (aucune méthode, aucun Add constructeur sans paramètre, et ainsi de suite), il est donc souvent utile d’appliquer l’attribut CollectionDataContractAttribute à des fins de débogage.

Nommage de collection

Voici une liste de règles d’affectation de noms de collection :

  • 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 mappent aux types XSD intégrés, ainsi que les types char, Timespan, et Guid, sont considérés comme des primitives à cet effet.

  • 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’entiers est « ArrayOfint ». N’oubliez pas que le nom du contrat de données de Object est « anyType », alors le nom du contrat de données des listes non génériques comme ArrayList est « ArrayOfanyType ».

Le nom par défaut des contrats de données des collections de dictionnaires, sauf substitution avec Name, est la chaîne « ArrayOfKeyValueOf » combinée avec le nom du contrat de données du type de clé suivi du 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 de chaîne et d’entier 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 hachages d’espace de noms, consultez Noms des contrats de données.

Chaque contrat de données de collecte de dictionnaire a un contrat de données complémentaire 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 à l’aide de la ItemName propriété, comme décrit dans la section suivante.

Règles de nommage de type générique, comme décrit dans Les noms de contrat de données, s’appliquent entièrement aux types de collection ; autrement dit, vous pouvez utiliser des accolades dans Name pour indiquer les paramètres de type générique. Toutefois, les nombres dans les accolades font référence aux paramètres génériques et non aux types contenus dans la collection.

Personnalisation de la collection

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

Règles de polymorphisme

Comme mentionné précédemment, la personnalisation des collections à l’aide de l’attribut CollectionDataContractAttribute peut interférer avec l’interchangeabilité des collections. Deux types de collection personnalisés ne peuvent être considérés comme équivalents que si leur nom, leur espace de noms, leur nom d’élément, ainsi que les noms de clé et de valeur (s’il s’agit de collections de dictionnaires) correspondent.

En raison de personnalisations, il est possible d’utiliser par inadvertance un contrat de données de collecte où un autre 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 affectée à testMarks. Toutefois, Marks2 ne doit pas être utilisé, car son contrat de données n’est pas considéré comme équivalent au contrat de IList<int> données. Le nom du contrat de données est « Marks2 » et non « ArrayOfint », et le nom de l’élément répétitif est «< mark> » et non «< int> ».

Les règles du tableau suivant s’appliquent à l’affectation polymorphe de collections.

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

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

La personnalisation n’est pas utilisée.*
Collection non personnalisée Le nom du 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 NetDataContractSerializer classe sérialise également le nom de type réel dans ce cas, de sorte 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 du contrat est sérialisé, le type de collection affecté doit se trouver dans la liste des types connus. L’inverse est également vrai : dans les cas où le nom n’est pas sérialisé, l’ajout du type à la liste des types connus n’est pas obligatoire.

Un tableau d’un type dérivé peut être affecté à un tableau d’un type de base. Dans ce cas, le nom du 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 affecter un tableau de Book à un tableau de LibraryItem. Cela ne s’applique pas aux autres types de collection. Par exemple, vous ne pouvez pas affecter un Generic List of Book à un Generic List of LibraryItem. Toutefois, vous pouvez affecter un Generic List of LibraryItem qui contient des instances de Book. Dans le cas du tableau et du cas non-matriciel, Book doit figurer dans la liste des types connus.

Collections et conservation des références d’objet

Lorsqu’un sérialiseur fonctionne dans un mode où il conserve les références d’objet, la conservation des références d’objet s’applique également aux collections. Plus précisément, l’identité d’objet est conservée pour les collections entières et les éléments individuels contenus dans les collections. Pour les dictionnaires, l’identité d’objet est conservée à la fois pour les objets de paire clé/valeur et les objets clé et valeur individuels.

Voir aussi