Share via


Tipi di raccolta nei contratti di dati

Una raccolta costituisce un elenco di elementi di un certo tipo. In .NET Framework tali elenchi possono essere rappresentati mediante matrici o una varietà di altri tipi (elenco generico, BindingList<T> generico, StringCollection o ArrayList). Una raccolta, ad esempio, può contenere un elenco di indirizzi per un determinato cliente. Queste raccolte vengono denominate raccolte di elenchi, indipendentemente dal tipo effettivo.

Esiste una forma speciale di raccolta che rappresenta un'associazione tra un elemento (la "chiave") e un altro elemento (il "valore"). In .NET Framework questa raccolta è rappresentata da tipi quali Hashtable e il dizionario generico. Una raccolta di associazioni, ad esempio, può eseguire il mapping di una città (la "chiave") alla relativa popolazione (il "valore") Queste raccolte vengono denominate raccolte di dizionario, indipendentemente dal tipo effettivo.

Le raccolte ricevono un trattamento speciale nel modello del contratto dati.

I tipi che implementano l'interfaccia IEnumerable , tra cui matrici e raccolte generiche, vengono riconosciuti come raccolte. Di questi, i tipi che implementano l'interfaccia IDictionary o l'interfaccia IDictionary<TKey,TValue> generica sono raccolte di dizionario mentre tutti gli altri sono raccolte di elenco.

Nelle sezioni seguenti vengono descritti dettagliatamente gli ulteriori requisiti relativi ai tipi di raccolte, ad esempio la presenza di un metodo denominato Add e di un costruttore senza parametri. La presenza di questi requisiti garantisce che i tipi di raccolta possano essere serializzati e deserializzati Ciò implica inoltre che alcune raccolte non sono supportate direttamente, ad esempio l'oggetto ReadOnlyCollection<T> generico (perché non dispone di un costruttore senza parametri). Per informazioni su come evitare queste restrizioni, tuttavia, vedere la sezione "Utilizzo di tipi di interfacce di raccolta e raccolte di sola lettura" più avanti in questo argomento.

I tipi contenuti nelle raccolte devono essere tipi di contratto dati o devono poter essere serializzati in altro modo. Per altre informazioni, vedere Tipi supportati dal serializzatore dei contratti di dati.

Per altre informazioni sulla distinzione tra raccolta valida e raccolta non valida, nonché sulla modalità di serializzazione delle raccolte, vedere le informazioni sulla serializzazione delle raccolte nella sezione "Regole avanzate di inserimento in raccolte" di questo argomento.

Raccolte intercambiabili

Si suppone che tutte le raccolte di elenco dello stesso tipo dispongano dello stesso contratto dati (a meno che non vengano personalizzate con l'attributo CollectionDataContractAttribute , come viene descritto più avanti in questo argomento). Pertanto, ad esempio, i contratti dati seguenti sono equivalenti.

[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

Entrambi i contratti dati generano XML simile al codice seguente.

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

L'intercambiabilità delle raccolte consente di utilizzare, ad esempio, un tipo di raccolta ottimizzato per le prestazioni nel server e un tipo di raccolta progettato per essere associato ai componenti dell'interfaccia utente nel client.

Come per le raccolte di elenco, si suppone che tutte le raccolte di dizionario con lo stesso tipo di chiave e valore dispongano dello stesso contratto dati (a meno che non vengano personalizzate con l'attributo CollectionDataContractAttribute ).

Ai fini dell'equivalenza delle raccolte vale solo il tipo di contratto dati, non i tipi .NET. Una raccolta Type1 è considerata equivalente a una raccolta Type2 se Type1 e Type2 presentano contratti dati equivalenti.

Si suppone che raccolte non generiche dispongano dello stesso contratto dati di raccolte generiche di tipo Object. Ad esempio, i contratti dati per ArrayList e per un oggetto List<T> generico di Object sono uguali.

Utilizzo di tipi di interfacce di raccolta e raccolte di sola lettura

Anche per i tipi di interfaccia di raccolta (IEnumerable, IDictionary, IDictionary<TKey,TValue>generico o interfacce che derivano da queste interfacce) si suppone che dispongano di contratti dati di raccolta, equivalenti a contratti dati di raccolta per tipi di raccolta effettivi. È dunque possibile dichiarare il tipo serializzato come tipo di interfaccia di raccolta e i risultati sono gli stessi che verrebbero ottenuti se fosse utilizzato un tipo di raccolta effettivo. I contratti dati seguenti, ad esempio, sono equivalenti.

[DataContract(Name="Customer")]
public class Customer1
{
    [DataMember]
    public string customerName;
    [DataMember]
    public Collection<Address> addresses;
}

[DataContract(Name="Customer")]
public class Customer2
{
    [DataMember]
    public string customerName;
    [DataMember]
    public ICollection<Address> addresses;
}
<DataContract(Name:="Customer")>
Public Class Customer1

    <DataMember()>
    Public customerName As String

    <DataMember()>
    Public addresses As Collection(Of Address)

End Class

<DataContract(Name:="Customer")>
Public Class Customer2

    <DataMember()>
    Public customerName As String

    <DataMember()>
    Public addresses As ICollection(Of Address)

End Class

Durante la serializzazione, quando il tipo dichiarato è un'interfaccia, il tipo dell'istanza effettivo utilizzato può essere qualsiasi tipo che implementa quell'interfaccia. Le restrizioni discusse in precedenza (la presenza di un costruttore parametro e di un metodo Add ) non sono applicabili. È ad esempio possibile impostare indirizzi in Customer2 su un'istanza della classe ReadOnlyCollection<T> generica di Address, anche se non si può dichiarare direttamente un membro dati di tipo ReadOnlyCollection<T>generico.

Durante la deserializzazione, quando il tipo dichiarato è un'interfaccia, il motore di serializzazione sceglie un tipo che implementi l'interfaccia dichiarata e viene creata un'istanza del tipo. Il meccanismo dei tipi noti (descritto in Data Contract Known Types) non ha alcun effetto in questo caso. La scelta del tipo è incorporata in WCF.

Personalizzazione dei tipi di raccolta

È possibile personalizzare i tipi di raccolta utilizzando l'attributo CollectionDataContractAttribute , che presenta molti utilizzi.

Si noti che la personalizzazione dei tipi di raccolta compromette l'intercambiabilità delle raccolte, pertanto si consiglia in genere di evitare l'applicazione di questo attributo quando possibile. Per altre informazioni su questo problema, vedere la sezione "Regole avanzate di inserimento in raccolte" più avanti in questo argomento.

Denominazione del contratto dati della raccolta

Le regole per la denominazione dei tipi di raccolta sono simili a quelle per la denominazione dei tipi di contratto dati normali, come viene descritto in Data Contract Names, ad eccezione di alcune importanti differenze:

  • Per personalizzare il nome viene utilizzato l'attributo CollectionDataContractAttribute anziché l'attributo DataContractAttribute . Anche l'attributo CollectionDataContractAttribute dispone delle proprietà Name e Namespace .

  • Quando l'attributo CollectionDataContractAttribute non viene applicato, il nome e lo spazio dei nomi predefiniti per i tipi di raccolta dipendono dai nomi e dagli spazi dei nomi dei tipi contenuti nella raccolta. Non vengono influenzati dal nome e dallo spazio dei nomi del tipo di raccolta stesso. Per un esempio, vedere i tipi seguenti.

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

Il nome del contratto dati di entrambi i tipi è "ArrayOfstring" e non "CustomerList1" o "StringList1". Ciò significa che eseguendo la serializzazione di uno qualsiasi di questi tipi a livello di radice si ottiene codice XML simile al seguente:

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

Questa regola di denominazione è stata scelta per avere la certezza che i tipi non personalizzati che rappresentano un elenco di stringhe abbiano lo stesso contratto dati e la stessa rappresentazione XML. In questo modo l'intercambiabilità delle raccolte è possibile. Nell'esempio CustomerList1 e StringList1 sono completamente intercambiabili.

Quando viene applicato l'attributo CollectionDataContractAttribute , tuttavia, la raccolta diventa un contratto dati di una raccolta personalizzata, anche se nell'attributo non è impostata alcuna proprietà. Il nome e lo spazio dei nomi del contratto dati della raccolta dipendono quindi dal tipo di raccolta stesso. Per un esempio, vedere il tipo seguente.

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

Quando viene serializzato, l'XML risultante è simile al codice seguente.

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

Si noti che questo codice non è più equivalente alla rappresentazione XML dei tipi non personalizzati.

  • È quindi possibile utilizzare le proprietà Name e Namespace per personalizzare ulteriormente la denominazione. Fare riferimento alla classe seguente.

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

L'XML risultante è simile al codice seguente.

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

Per altre informazioni, vedere la sezione "Regole avanzate di inserimento in raccolte" più avanti in questo argomento.

Personalizzazione del nome dell'elemento ripetuto nelle raccolte di elenco

Le raccolte di elenco contengono voci ripetute. Normalmente ogni voce ripetuta è rappresentata come un elemento denominato secondo il nome del contratto dati del tipo contenuto nella raccolta.

Negli esempi CustomerList , le raccolte contengono stringhe. Poiché il nome del contratto di dati per il tipo primitivo della stringa è "string", l'elemento ripetuto è "<string>".

Utilizzando la proprietà ItemName nell'attributo CollectionDataContractAttribute , tuttavia, questo nome di elemento ripetuto può essere personalizzato. Per un esempio, vedere il tipo seguente.

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

L'XML risultante è simile al codice seguente.

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

Lo spazio dei nomi dell'elemento ripetuto è sempre uguale allo spazio dei nomi del contratto dati della raccolta, che può essere personalizzato tramite la proprietà Namespace , come descritto in precedenza.

Personalizzazione di raccolte di dizionario

Le raccolte di dizionario sono essenzialmente elenchi di voci, in cui ogni voce presenta una chiave seguita da un valore. Come per gli elenchi normali, è possibile modificare il nome di elemento corrispondente all'elemento ripetuto utilizzando la proprietà ItemName .

È inoltre possibile modificare i nomi di elemento che rappresentano la chiave e il valore utilizzando le proprietà KeyName e ValueName . Gli spazi dei nomi di questi elementi sono gli stessi dello spazio dei nomi del contratto dati della raccolta.

Per un esempio, vedere il tipo seguente.

[CollectionDataContract
    (Name = "CountriesOrRegionsWithCapitals",
    ItemName = "entry",
    KeyName = "countryorregion",
    ValueName = "capital")]
public class CountriesOrRegionsWithCapitals2 : Dictionary<string, string> { }
<CollectionDataContract(Name:="CountriesOrRegionsWithCapitals",
                        ItemName:="entry", KeyName:="countryorregion",
                        ValueName:="capital")>
Public Class CountriesOrRegionsWithCapitals2
    Inherits Dictionary(Of String, String)
End Class

Quando viene serializzato, l'XML risultante è simile al codice seguente.

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

Per altre informazioni sulle raccolte di dizionario, vedere la sezione "Regole avanzate di inserimento in raccolte" più avanti in questo argomento.

Raccolte e tipi noti

Non è necessario aggiungere tipi di raccolta a tipi noti quando vengono utilizzati polimorficamente in luogo di altre raccolte o di altre interfacce di raccolta. Se ad esempio si dichiara un membro dati di tipo IEnumerable e lo si utilizza per inviare un'istanza di ArrayList, non è necessario aggiungere ArrayList a tipi noti.

Quando si utilizzano raccolte in modo polimorfico al posto di tipi diversi da raccolte, è necessario aggiungerli ai tipi noti. Ad esempio, se si dichiara un membro dati di tipo Object e lo si utilizza per inviare un'istanza di ArrayList, aggiungere ArrayList ai tipi noti.

Ciò non consente di serializzare eventuali raccolte equivalenti polimorficamente. Ad esempio, quando si aggiunge ArrayList all'elenco di tipi noti nell'esempio precedente, non è consentito assegnare la classe Array of Object , anche se presenta un contratto dati equivalente. Il comportamento normale di tipi noti nel caso della serializzazione per tipi diversi da raccolte non è diverso, ma è particolarmente importante essere a conoscenza di tale funzionamento nel caso delle raccolte perché capita frequentemente che si equivalgano.

Durante la serializzazione solo un tipo può essere conosciuto in un determinato ambito per un contratto dati specificato e le raccolte equivalenti presentano tutti gli stessi contratti dati. Nell'esempio precedente, ciò significa che non è possibile aggiungere sia ArrayList sia Array of Object ai tipi noti nello stesso ambito. Anche questo caso è equivalente al comportamento dei tipi noti per tipi diversi da raccolte ma è particolarmente importante esserne a conoscenza per quanto riguarda le raccolte.

È inoltre possibile che i tipi noti siano necessari come contenuto di raccolte. Se un elemento ArrayList , ad esempio, contiene effettivamente istanze di Type1 e Type2, entrambi questi tipi devono essere aggiunti ai tipi noti.

Nell'esempio seguente viene illustrato un oggetto grafico costruito correttamente in cui sono utilizzati raccolte e tipi noti. L'esempio è stato studiato appositamente in quanto in un'applicazione effettiva i membri dati seguenti non verrebbero normalmente definiti Objecte quindi non si verificherebbero problemi di polimorfismo/tipi noti.

[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

In fase di deserializzazione, se il tipo dichiarato è un tipo di raccolta, ne viene creata un'istanza indipendentemente dal tipo effettivamente inviato. Se il tipo dichiarato è un'interfaccia di raccolta, il deserializzatore sceglie un tipo di cui creare un'istanza senza considerare i tipi noti.

In fase di deserializzazione, inoltre, se il tipo dichiarato non è un tipo di raccolta ma viene inviato un tipo di raccolta, dall'elenco dei tipi noti viene selezionato un tipo di raccolta corrispondente. In fase di deserializzazione è possibile aggiungere tipi di interfaccia di raccolta all'elenco di tipi noti. Anche in questo caso il motore di deserializzazione sceglie un tipo di cui creare un'istanza.

Raccolte e classe NetDataContractSerializer

Quando si utilizza la classe NetDataContractSerializer , i tipi di raccolta non personalizzati (senza l'attributo CollectionDataContractAttribute ) che non sono matrici perdono il relativo significato speciale.

Tipi di raccolta non personalizzati contrassegnati con l'attributo SerializableAttribute possono comunque essere serializzati dalla classe NetDataContractSerializer secondo l'attributo SerializableAttribute o le regole dell'interfaccia ISerializable .

Tipi di raccolta personalizzati, interfacce di raccolta e matrici vengono comunque considerati raccolte, anche quando la classe NetDataContractSerializer è in uso.

Raccolte e schema

Tutte le raccolte equivalenti presentano la stessa rappresentazione nello schema XSD (XML Schema Definition Language). Per questo motivo in genere non si ottiene, nel codice client generato, lo stesso tipo di raccolta del codice generato nel server. Il server, ad esempio, può utilizzare un contratto dati con List<T> generico del membro dati Integer ma nel codice client generato lo stesso membro dati può diventare una matrice di numeri interi.

Le raccolte di dizionario sono contrassegnate con un'annotazione dello schema specifica di WCF che indica che si tratta di dizionari. In caso contrario, non sarebbero distinguibili da semplici elenchi contenenti voci con una chiave e un valore. Per una descrizione più precisa del modo in cui le raccolte vengono rappresentate nello schema del contratto dati, vedere Data Contract Schema Reference.

Per impostazione predefinita i tipi non vengono generati per raccolte non personalizzate nel codice importato. I membri dati di tipi di raccolta di elenco sono importati come matrici e i membri dati di tipi di raccolta di dizionario sono importati come dizionario generico.

Per le raccolte personalizzate, tuttavia, vengono generati tipi separati, contrassegnati con l'attributo CollectionDataContractAttribute . (il tipo di raccolta personalizzato contenuto nello schema è un tipo che non utilizza spazio dei nomi, nome, elemento ripetuto o nomi di elementi chiave/valore predefiniti). Si tratta di tipi vuoti che derivano da List<T> generico per i tipi di elenco e dal dizionario generico per i tipi di dizionario.

È ad esempio possibile che nel server siano presenti i tipi seguenti.

[DataContract]
public class CountryOrRegion
{
    [DataMember]
    public Collection<string> officialLanguages;
    [DataMember]
    public List<DateTime> holidays;
    [DataMember]
    public CityList cities;
    [DataMember]
    public ArrayList otherInfo;
}

public class Person
{
    public Person(string fName, string lName)
    {
        this.firstName = fName;
        this.lastName = lName;
    }

    public string firstName;
    public string lastName;
}

public class PeopleEnum : IEnumerator
{
    public Person[] _people;

    // Enumerators are positioned before the first element
    // until the first MoveNext() call.
    int position = -1;

    public PeopleEnum(Person[] list)
    {
        _people = list;
    }

    public bool MoveNext()
    {
        position++;
        return (position < _people.Length);
    }

    public void Reset()
    {
        position = -1;
    }

    public object Current
    {
        get
        {
            try
            {
                return _people[position];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }
}

[CollectionDataContract(Name = "Cities", ItemName = "city", KeyName = "cityName", ValueName = "population")]
public class CityList : IDictionary<string, int>, IEnumerable<System.Collections.Generic.KeyValuePair<string, int>>
{
    private Person[] _people = null;

    public bool ContainsKey(string s) { return true; }
    public bool Contains(string s) { return true; }
    public bool Contains(KeyValuePair<string, int> item) { return (true); }
    public void Add(string key, int value) { }
    public void Add(KeyValuePair<string, int> keykValue) { }
    public bool Remove(string s) { return true; }
    public bool TryGetValue(string d, out int i)
    {
        i = 0; return (true);
    }

    /*
    [TypeConverterAttribute(typeof(SynchronizationHandlesTypeConverter))]
    public ICollection<string> SynchronizationHandles {
        get { return (System.Collections.Generic.ICollection<string>) new Stack<string> (); }
        set { }
    }*/

    public ICollection<string> Keys
    {
        get
        {
            return (System.Collections.Generic.ICollection<string>)new Stack<string>();
        }
    }

    public int this[string s]
    {
        get
        {
            return 0;
        }
        set
        {
        }
    }

    public ICollection<int> Values
    {
        get
        {
            return (System.Collections.Generic.ICollection<int>)new Stack<string>();
        }
    }

    public void Clear() { }
    public void CopyTo(KeyValuePair<string, int>[] array, int index) { }
    public bool Remove(KeyValuePair<string, int> item) { return true; }
    public int Count { get { return 0; } }
    public bool IsReadOnly { get { return true; } }

    IEnumerator<KeyValuePair<string, int>>
        System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, int>>.GetEnumerator()
    {
        return (IEnumerator<KeyValuePair<string, int>>)new PeopleEnum(_people); ;
    }

    public IEnumerator GetEnumerator()
    {
        return new PeopleEnum(_people);
    }
}

<DataContract()>
Public Class CountryOrRegion

    <DataMember()>
    Public officialLanguages As Collection(Of String)

    <DataMember()>
    Public holidays As List(Of DateTime)

    <DataMember()>
    Public cities As CityList

    <DataMember()>
    Public otherInfo As ArrayList

End Class

Public Class Person
    Public Sub New(ByVal fName As String, ByVal lName As String)
        Me.firstName = fName
        Me.lastName = lName
    End Sub

    Public firstName As String
    Public lastName As String
End Class

Public Class PeopleEnum
    Implements IEnumerator

    Public _people() As Person
    ' Enumerators are positioned before the first element
    ' until the first MoveNext() call.
    Private position As Integer = -1

    Public Sub New(ByVal list() As Person)
        _people = list
    End Sub

    Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
        position += 1
        Return position < _people.Length
    End Function

    Public Sub Reset() Implements IEnumerator.Reset
        position = -1
    End Sub

    Public ReadOnly Property Current() As Object Implements IEnumerator.Current
        Get
            Try
                Return _people(position)
            Catch e1 As IndexOutOfRangeException
                Throw New InvalidOperationException()
            End Try
        End Get
    End Property
End Class

<CollectionDataContract(Name:="Cities",
                        ItemName:="city",
                        KeyName:="cityName",
                        ValueName:="population")>
Public Class CityList
    Implements IDictionary(Of String, Integer), IEnumerable(Of System.Collections.Generic.KeyValuePair(Of String, Integer))

    Private _people() As Person = Nothing

    Public Function ContainsKey(ByVal s As String) As Boolean Implements IDictionary(Of String, Integer).ContainsKey
        Return True
    End Function

    Public Function Contains(ByVal s As String) As Boolean
        Return True
    End Function

    Public Function Contains(ByVal item As KeyValuePair(Of String, Integer)) As Boolean Implements IDictionary(Of String, Integer).Contains
        Return (True)
    End Function

    Public Sub Add(ByVal key As String,
                   ByVal value As Integer) Implements IDictionary(Of String, Integer).Add
    End Sub

    Public Sub Add(ByVal keykValue As KeyValuePair(Of String, Integer)) Implements IDictionary(Of String, Integer).Add
    End Sub

    Public Function Remove(ByVal s As String) As Boolean Implements IDictionary(Of String, Integer).Remove
        Return True
    End Function

    Public Function TryGetValue(ByVal d As String,
                                <System.Runtime.InteropServices.Out()> ByRef i As Integer) _
                                As Boolean Implements IDictionary(Of String, Integer).TryGetValue
        i = 0
        Return (True)
    End Function

    Public ReadOnly Property Keys() As ICollection(Of String) Implements IDictionary(Of String, Integer).Keys
        Get
            Return CType(New Stack(Of String)(), System.Collections.Generic.ICollection(Of String))
        End Get
    End Property

    Default Public Property Item(ByVal s As String) As Integer Implements IDictionary(Of String, Integer).Item
        Get
            Return 0
        End Get
        Set(ByVal value As Integer)
        End Set
    End Property

    Public ReadOnly Property Values() As ICollection(Of Integer) Implements IDictionary(Of String, Integer).Values
        Get
            Return CType(New Stack(Of String)(), System.Collections.Generic.ICollection(Of Integer))
        End Get
    End Property

    Public Sub Clear() Implements IDictionary(Of String, Integer).Clear
    End Sub

    Public Sub CopyTo(ByVal array() As KeyValuePair(Of String, Integer),
                      ByVal index As Integer) Implements IDictionary(Of String, Integer).CopyTo
    End Sub

    Public Function Remove(ByVal item As KeyValuePair(Of String, Integer)) As Boolean Implements IDictionary(Of String, Integer).Remove
        Return True
    End Function

    Public ReadOnly Property Count() As Integer Implements IDictionary(Of String, Integer).Count
        Get
            Return 0
        End Get
    End Property

    Public ReadOnly Property IsReadOnly() As Boolean Implements IDictionary(Of String, Integer).IsReadOnly
        Get
            Return True
        End Get
    End Property

    Private Function IEnumerable_GetEnumerator() As IEnumerator(Of KeyValuePair(Of String, Integer)) _
        Implements System.Collections.Generic.IEnumerable(Of System.Collections.Generic.KeyValuePair(Of String, Integer)).GetEnumerator

        Return CType(New PeopleEnum(_people), IEnumerator(Of KeyValuePair(Of String, Integer)))
    End Function

    Public Function GetEnumerator() As IEnumerator Implements System.Collections.IEnumerable.GetEnumerator

        Return New PeopleEnum(_people)

    End Function

End Class

Quando lo schema viene esportato per poi essere nuovamente importato, il codice client generato è simile al seguente (per facilità di lettura vengono mostrati i campi anziché le proprietà):

[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

È consigliabile utilizzare tipi diversi nel codice generato anziché i tipi predefiniti. È ad esempio opportuno utilizzare BindingList<T> generico anziché matrici normali dei membri dati per semplificarne l'associazione ai componenti dell'interfaccia utente.

Per scegliere i tipi di raccolta da generare, durante l'importazione dello schema passare un elenco dei tipi di raccolta che si desidera utilizzare nella proprietà ReferencedCollectionTypes dell'oggetto ImportOptions . Questi tipi sono chiamati tipi di raccolta a cui viene fatto riferimento.

Quando si fa riferimento a tipi generici, deve trattarsi di generics completamente aperti o di generics completamente chiusi.

Nota

Quando si usa lo strumento Svcutil.exe, questo riferimento può essere eseguito usando l'opzione della riga di comando /collectionType (forma breve: /ct). È importante ricordare che è anche necessario specificare l'assembly per i tipi di raccolta a cui viene fatto riferimento usando l'opzione /reference (forma breve: /r). Se il tipo è generico, deve essere seguito da una virgoletta inversa e dal numero di parametri generici. La virgoletta inversa (`) non deve essere confusa con il carattere della virgoletta singola ('). È possibile specificare più tipi di raccolta a cui viene fatto riferimento usando l'opzione /collectionType più di una volta.

Ad esempio, per fare in modo che tutti gli elenchi vengano importati come oggetto List<T>generico.

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

Quando si importa una raccolta, l'elenco di tipi di raccolta a cui si fa riferimento viene analizzato e viene utilizzata la raccolta con la migliore corrispondenza trovata, come tipo di membro dati (per le raccolte non personalizzate) o come tipo di base per la derivazione (per le raccolte personalizzate). I dizionari vengono associati solo a dizionari mentre gli elenchi a elenchi.

Se ad esempio si aggiungono la classe BindingList<T> generica e la classe Hashtable all'elenco di tipi a cui viene fatto riferimento, il codice client generato per l'esempio precedente sarà simile al seguente:

[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

È possibile specificare i tipi di interfaccia di raccolta nell'ambito dei tipi di raccolta a cui si fa riferimento, ma non è possibile specificare tipi di raccolta non validi (ad esempio tipi senza il metodo Add o il costruttore pubblico).

Un generico chiuso è considerato la corrispondenza migliore (i tipi non generici sono considerati equivalenti ai generics chiusi di Object). Se ad esempio i tipi List<T> generico di DateTime, BindingList<T> generico (generico aperto) e ArrayList sono tipi di raccolta a cui viene fatto riferimento, viene generato il codice seguente:

[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

Per le raccolte di elenchi, sono supportati solo i casi illustrati nella tabella seguente:

Tipo a cui viene fatto riferimento Interfaccia implementata dal tipo a cui viene fatto riferimento Esempio Tipo trattato come:
Non generico o generico chiuso (qualsiasi numero di parametri) Non generico MyType : IList

or

MyType<T> : IList

dove T= int
Generico chiuso di Object (ad esempio, IList<object>)
Non generico o generico chiuso (qualsiasi numero di parametri che non corrispondono necessariamente al tipo di raccolta) Generico chiuso MyType : IList<string>

or

MyType<T> : IList<string> dove T=int
Generico chiuso (ad esempio IList<string>)
Generico chiuso con qualsiasi numero di parametri Generico aperto che utilizza qualsiasi parametro del tipo MyType<T,U,V> : IList<U>

dove T=int, U=string, V=bool
Generico chiuso (ad esempio IList<string>)
Generico aperto con un parametro Generico aperto che utilizza il parametro del tipo MyType<T> : IList<T>, T è aperto Generico aperto (ad esempio IList<T>)

Se un tipo implementa più di un'interfaccia della raccolta di elenco, vengono applicate le restrizioni seguenti:

  • Se il tipo implementa IEnumerable<T> generico (o le interfacce derivate) più volte per tipi diversi, non viene considerato un tipo di raccolta a cui fare riferimento valido e viene ignorato. Questa condizione è vera anche se alcune implementazioni non sono valide o utilizzano generics aperti. Un tipo che implementa IEnumerable<T> generico di int e IEnumerable<T> generico di T, ad esempio, non verrebbe mai utilizzato come raccolta a cui si fa riferimento di int o di qualsiasi altro tipo, indipendentemente dalla circostanza che il tipo abbia un metodo Add che accetta int o un metodo Add che accetta un parametro di tipo T o entrambi.

  • Se il tipo implementa un'interfaccia di raccolta generica oltre a IList, non viene mai utilizzato come tipo di raccolta a cui si fa riferimento, a meno che l'interfaccia di raccolta generica non sia una generica chiusa di tipo Object.

Per le raccolte di dizionario, sono supportati solo i casi illustrati nella tabella seguente:

Tipo a cui viene fatto riferimento Interfaccia implementata dal tipo a cui viene fatto riferimento Esempio Tipo trattato come
Non generico o generico chiuso (qualsiasi numero di parametri) IDictionary MyType : IDictionary

or

MyType<T> : IDictionary dove T=int
Generico chiuso IDictionary<object,object>
Generico chiuso (qualsiasi numero di parametri) IDictionary<TKey,TValue>, chiuso MyType<T> : IDictionary<string, bool> dove T=int Generico chiuso (ad esempio IDictionary<string,bool>)
Generico chiuso (qualsiasi numero di parametri) IDictionary<TKey,TValue>generico, la chiave o il valore è chiuso, l'altro è aperto e utilizza uno dei parametri del tipo MyType<T,U,V> : IDictionary<string,V> dove T=int, U=float, V=bool

or

MyType<Z> : IDictionary<Z,bool> dove Z=string
Generico chiuso (ad esempio IDictionary<string,bool>)
Generico chiuso (qualsiasi numero di parametri) IDictionary<TKey,TValue>generico, chiave e valore sono aperti e ognuno utilizza uno dei parametri del tipo MyType<T,U,V> : IDictionary<V,U> dove T=int, U=bool, V=string Generico chiuso (ad esempio IDictionary<string,bool>)
Generico aperto (due parametri) IDictionary<TKey,TValue>generico, aperto, utilizza entrambi i parametri generici del tipo nell'ordine in cui sono visualizzati. MyType<K,V> : IDictionary<K,V>, K e V entrambi aperti Generico aperto (ad esempio IDictionary<K,V>)

Se il tipo implementa sia IDictionary che IDictionary<TKey,TValue>generico, solo IDictionary<TKey,TValue> generico viene considerato.

Il riferimento a tipi generici parziali non è supportato.

I duplicati non sono consentiti, ad esempio non è possibile aggiungere l'oggetto List<T> generico di Integer e la raccolta generica di Integer a ReferencedCollectionTypes, perché ciò renderebbe impossibile stabilire quale utilizzare quando nello schema viene trovato un elenco di valori integer. I duplicati vengono rilevati solo se nello schema esiste un tipo che espone il problema dei duplicati. Se ad esempio lo schema importato non contiene elenchi di numeri interi, è consentito disporre sia di List<T> generico di Integer che della raccolta generica di Integer nella proprietà ReferencedCollectionTypes, ma nessuno esercita alcun effetto.

Regole avanzate di inserimento in raccolte

Serializzazione delle raccolte

Di seguito vengono elencate le regole per la serializzazione delle raccolte:

  • La combinazione di tipi di raccolta (con raccolte di raccolte) è consentita. Le matrici di matrici vengono trattate come raccolte di raccolte. Le matrici multidimensionali non sono supportate.

  • Matrici di byte e matrici di XmlNode sono tipi speciali di matrici trattati come primitivi, non come raccolte. La serializzazione di una matrice di byte genera un singolo elemento XML contenente un blocco di dati con codifica Base64 anziché un elemento separato per ogni byte. Per altre informazioni sul modo in cui viene considerata una matrice di XmlNode, vedere Tipi XML e ADO.NET nei contratti di dati. Questi tipi speciali, naturalmente, possono fare parte delle raccolte: una matrice di matrice di byte sfocia in più elementi XML, ognuno dei quali contiene un blocco di dati con codifica Base64.

  • Se l'attributo DataContractAttribute viene applicato a un tipo di raccolta, il tipo viene trattato come tipo di contratto dati normale, non come una raccolta.

  • Se un tipo di raccolta implementa l'interfaccia IXmlSerializable , vengono applicate le regole seguenti, dato un tipo myType:IList<string>, IXmlSerializable:

    • Il tipo di dichiarato è IList<string>, il tipo è serializzato come elenco.

    • Il tipo di dichiarato è myType, ed è serializzato come IXmlSerializable.

    • Se il tipo dichiarato è IXmlSerializable, viene serializzato come IXmlSerializable, ma solo se si aggiunge myType all'elenco dei tipi noti.

  • Le raccolte vengono serializzate e deserializzate tramite i metodi illustrati nella tabella seguente:

Il tipo di raccolta implementa Metodo/i chiamato/i durante la serializzazione Metodo/i chiamato/i durante la deserializzazione
Generico IDictionary<TKey,TValue> get_Keys, get_Values Add generico
IDictionary get_Keys, get_Values Add
Generico IList<T> Indicizzatore IList<T> generico Add generico
Generico ICollection<T> Enumeratore Add generico
IList IndicizzatoreIList Add
Generico IEnumerable<T> GetEnumerator Metodo non statico denominato Add che accetta un parametro del tipo appropriato (il tipo del parametro generico o uno dei tipi di base). È necessario che tale metodo esista affinché il serializzatore tratti un tipo di raccolta come raccolta sia durante la serializzazione che durante la deserializzazione.
IEnumerable (e di conseguenza ICollection, interfaccia derivata) GetEnumerator Metodo non statico denominato Add che accetta un parametro di tipo Object. È necessario che tale metodo esista affinché il serializzatore tratti un tipo di raccolta come raccolta sia durante la serializzazione che durante la deserializzazione.

Nella tabella precedente sono elencate le interfacce di raccolta in ordine decrescente di precedenza. Se un tipo implementa sia IList che IEnumerable<T>generico, ad esempio, la raccolta viene serializzata e deserializzata secondo le regole di IList :

  • In fase di deserializzazione, tutte le raccolte vengono deserializzate creando innanzitutto un'istanza del tipo chiamando il costruttore senza parametri, che deve essere presente affinché il serializzatore sia in grado di trattare un tipo di raccolta come raccolta durante la serializzazione e la deserializzazione.

  • Se la stessa interfaccia di raccolta generica viene implementata più di una volta (ad esempio se un tipo implementa sia ICollection<T> generica di Integer che ICollection<T> generica di String) e non viene trovata nessuna interfaccia con un livello di precedenza maggiore, la raccolta non viene trattata come raccolta valida.

  • L'attributo SerializableAttribute può essere applicato ai tipi di raccolta, i quali possono implementare l'interfaccia ISerializable . Entrambi vengono ignorati. Se, tuttavia, il tipo non soddisfa pienamente i requisiti del tipo di raccolta (ad esempio, manca il metodo Add ), il tipo non viene considerato un tipo di raccolta, di conseguenza per stabilire se il tipo può essere serializzato, vengono utilizzati l'attributo SerializableAttribute e l'interfaccia ISerializable .

  • L'applicazione dell'attributo CollectionDataContractAttribute a una raccolta per personalizzarlo rimuove il meccanismo di fallback di SerializableAttribute precedente. Se invece una raccolta personalizzata non soddisfa i requisiti del tipo di raccolta, viene generata un'eccezione InvalidDataContractException . Poiché spesso la stringa dell'eccezione contiene informazioni che spiegano il motivo per il quale un determinato tipo non viene considerato una raccolta valida (nessun metodo Add, nessun costruttore senza parametri e così via), risulta utile applicare l'attributo CollectionDataContractAttribute a scopo di debug.

Denominazione di raccolte

Di seguito vengono elencate le regole di denominazione delle raccolte:

  • Lo spazio dei nomi predefinito per tutti i contratti dati delle raccolte di dizionario, nonché per contratti dati delle raccolte di elenco contenenti tipi primitivi, è http://schemas.microsoft.com/2003/10/Serialization/Arrays, a meno che non venga sottoposto a override utilizzando Namespace. I tipi che eseguono il mapping a tipi XSD incorporati, nonché i tipi char, Timespane Guid , vengono considerati primitivi a questo scopo.

  • Lo spazio dei nomi predefinito per tipi di raccolta che contengono tipi non primitivi corrisponde allo spazio dei nomi del contratto dati del tipo contenuto nella raccolta, a meno che non venga eseguito l'override utilizzando Namespace.

  • Il nome predefinito per i contratti dati delle raccolte di elenco, a meno che non venga sottoposto a override utilizzando Name, è la stringa "ArrayOf" associata al nome del contratto dati del tipo contenuto nella raccolta. Il nome del contratto dati per un elenco generico di numeri interi è, ad esempio, "ArrayOfint". È importante ricordare che il nome del contratto dati di Object è "anyType", quindi il nome del contratto dati di elenchi non generici come ArrayList è "ArrayOfanyType".

Il nome predefinito per i contratti dati delle raccolte di dizionario, a meno che non venga sottoposto a override utilizzando Name, è la stringa "ArrayOfKeyValueOf" associata al nome del contratto dati del tipo di chiave seguito dal nome del contratto dati del tipo di valore. Il nome del contratto dati per un dizionario generico di stringa e numero intero, ad esempio, è "ArrayOfKeyValueOfstringint". Inoltre, se il tipo di chiave o il tipo di valore non sono tipi primitivi, un hash di spazio dei nomi degli spazi dei nomi del contratto dati dei tipi di chiave e valore viene aggiunto al nome. Per altre informazioni sugli hash dello spazio dei nomi, vedere Nomi dei contratti di dati.

Ogni contratto dati della raccolta di dizionario dispone di un contratto dati complementare che rappresenta una voce del dizionario. Il nome è lo stesso del contratto dati del dizionario, ad eccezione del prefisso "ArrayOf", e lo spazio dei nomi corrisponde a quello del contratto dati del dizionario. Per il contratto dati del dizionario "ArrayOfKeyValueOfstringint", ad esempio, il contratto dati "KeyValueofstringint" rappresenta una voce del dizionario. È possibile personalizzare il nome di questo contratto dati utilizzando la proprietà ItemName , come viene descritto nella prossima sezione.

Le regole di denominazione dei tipi generici, descritte in Data Contract Names, si applicano completamente ai tipi di raccolta, ovvero è possibile usare parentesi graffe all'interno di Name per indicare parametri di tipi generici. Tuttavia, i numeri tra parentesi graffe si riferiscono a parametri generici e non a tipi contenuti nella raccolta.

Personalizzazione di raccolte

Gli utilizzi seguenti dell'attributo CollectionDataContractAttribute non sono consentiti e generano un'eccezione InvalidDataContractException :

Regole del polimorfismo

Come indicato in precedenza, la personalizzazione delle raccolte mediante l'attributo CollectionDataContractAttribute può interferire con l'intercambiabilità delle stesse. Due tipi di raccolta personalizzati possono essere considerati equivalenti solo se il nome, lo spazio dei nomi, il nome dell'elemento nonché il nome di chiave e valore (se raccolte di dizionari) corrispondono.

A causa delle personalizzazioni, è possibile utilizzare inavvertitamente il contratto dati di una raccolta laddove ne è previsto un altro. Questa evenienza deve essere evitata. Vedere i tipi seguenti.

[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

In questo caso, un'istanza di Marks1 può essere assegnata a testMarks. Marks2 , tuttavia, non deve essere utilizzato poiché il relativo contratto dati non viene considerato equivalente al contratto dati IList<int> . Il nome del contratto dati è "Marks2" e non "ArrayOfint" e il nome dell'elemento ripetuto è "<mark>" e non "<int>".

Per l'assegnazione polimorfica delle raccolte vengono applicate le regole riportate nella tabella seguente:

Tipo dichiarato Assegnazione di una raccolta non personalizzata Assegnazione di una raccolta personalizzata
Oggetto Il nome del contratto è serializzato. Il nome del contratto è serializzato.

Viene utilizzata la personalizzazione.
Interfaccia di raccolta Il nome del contratto non è serializzato. Il nome del contratto non è serializzato.

La personalizzazione non viene utilizzata.*
Raccolta non personalizzata Il nome del contratto non è serializzato. Il nome del contratto è serializzato.

Viene utilizzata la personalizzazione.**
Raccolta personalizzata Il nome del contratto è serializzato. La personalizzazione non viene utilizzata.** Il nome del contratto è serializzato.

Viene utilizzata la personalizzazione del tipo assegnato.**

*Con la classe NetDataContractSerializer , in questo caso viene utilizzata la personalizzazione. La classe NetDataContractSerializer serializza inoltre il nome effettivo del tipo in questo caso, quindi la deserializzazione viene eseguita in base alle previsioni.

**Questi casi determinano istanze non valide per lo schema, quindi devono essere evitati.

Nei casi in cui il nome del contratto è serializzato, il tipo di raccolta assegnato deve risultare nell'elenco dei tipi noti. È anche vero il contrario: nei casi in cui il nome non è serializzato, l'aggiunta del tipo all'elenco dei tipi noti non è necessaria.

Una matrice di un tipo derivato può essere assegnata a una matrice di un tipo di base. In questo caso il nome del contratto per il tipo derivato viene serializzato per ogni elemento ripetuto. Se ad esempio un tipo Book deriva dal tipo LibraryItem, è possibile assegnare una matrice di Book a una matrice di LibraryItem. Quanto esposto sopra non vale per altri tipi di raccolta. Ad esempio, non è possibile assegnare un oggetto Generic List of Book a un oggetto Generic List of LibraryItem. È tuttavia possibile assegnare un Generic List of LibraryItem contenente istanze di Book . In entrambi i casi, matrice e non matrice, Book deve essere presente nell'elenco dei tipi noti.

Raccolte e conservazione dei riferimenti all'oggetto

Quando un serializzatore opera in una modalità che consente di preservare i riferimenti all'oggetto, la conservazione dei riferimenti all'oggetto si applica anche alle raccolte. In particolare, l'identità dell'oggetto viene conservata sia per raccolte intere che per elementi singoli contenuti nelle raccolte. Per i dizionari, l'identità dell'oggetto viene conservata sia per oggetti coppia di chiave e valore che per oggetti chiave e valore singoli.

Vedi anche