Tipi di raccolta nei contratti 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 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 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 predefinito. La presenza di questi requisiti garantisce che i tipi di raccolta possano essere serializzati e deserializzati e implica inoltre che alcune raccolte non sono supportate direttamente, ad esempio l'elemento ReadOnlyCollection generico (perché non dispone di un costruttore predefinito) o Stack (perché il metodo per l'aggiunta di voci è denominato Push anziché Add). 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 ulteriori informazioni, vedere Tipi supportati dal serializzatore dei contratti dati.
Per ulteriori informazioni su 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;
}
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 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 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;
}
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 predefinito e di un metodo Add) non sono applicabili. È ad esempio possibile impostare indirizzi in Customer2 su un'istanza della classe ReadOnlyCollection generica di Address, anche se non si può dichiarare direttamente un membro dati di tipo ReadOnlyCollection 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 conosciuti (descritto in Tipi conosciuti di contratto dati) non esercita effetti 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 ulteriori 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 alle regole di denominazione dei tipi di contratto dati normali, come viene descritto in Nomi di contratto dati, nonostante 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> {}
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> {}
L'XML risultante è simile al codice seguente.
<cust_list>
<string>...</string>
<string>...</string>
<string>...</string>
...
</cust_list>
Per ulteriori 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 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> {}
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> { }
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 ulteriori informazioni su raccolte di dizionario, vedere la sezione "Regole avanzate di inserimento in raccolte" più avanti in questo argomento.
Raccolte e tipi conosciuti
Non è necessario aggiungere tipi di raccolta a tipi conosciuti 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 conosciuti.
Quando si utilizzano raccolte in modo polimorfico al posto di tipi diversi da raccolte, è necessario aggiungerli ai tipi conosciuti. 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 conosciuti.
Ciò non consente di serializzare eventuali raccolte equivalenti polimorficamente. Ad esempio, quando si aggiunge ArrayList all'elenco di tipi conosciuti nell'esempio precedente, non è consentito assegnare la classe Array of Object, anche se presenta un contratto dati equivalente. Il comportamento normale di tipi conosciuti 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 conosciuti nello stesso ambito. Anche questo caso è equivalente al comportamento dei tipi conosciuti per tipi diversi da raccolte ma è particolarmente importante esserne a conoscenza per quanto riguarda le raccolte.
È inoltre possibile che i tipi conosciuti 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 conosciuti.
Nell'esempio seguente viene illustrato un oggetto grafico costruito correttamente in cui sono utilizzati raccolte e tipi conosciuti. L'esempio è stato studiato appositamente in quanto in un'applicazione effettiva i membri dati seguenti non verrebbero normalmente definiti Object e quindi non si verificherebbero problemi di polimorfismo/tipi conosciuti.
[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
}
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 conosciuti.
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 conosciuti viene selezionato un tipo di raccolta corrispondente. In fase di deserializzazione è possibile aggiungere tipi di interfaccia di raccolta all'elenco di tipi conosciuti. 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 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 esatta del modo in cui le raccolte sono rappresentate nello schema del contratto dati, vedere Riferimento allo schema del contratto dati.
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 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);
}
}
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à):
È consigliabile utilizzare tipi diversi nel codice generato anziché i tipi predefiniti. È ad esempio opportuno utilizzare BindingList 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 utilizza lo strumento Svcutil.exe, questo riferimento può essere eseguito utilizzando 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 utilizzando 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 utilizzando l'opzione /collectionType più di una volta. |
Ad esempio, per fare in modo che tutti gli elenchi vengano importati come oggetto List 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 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 { }
È 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 generico di DateTime, BindingList 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> { }
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 |
oppure
dove T= int |
Generico chiuso di Object (ad esempio, |
Non generico o generico chiuso (qualsiasi numero di parametri che non corrispondono necessariamente al tipo di raccolta) |
Generico chiuso |
oppure
|
Generico chiuso (ad esempio |
Generico chiuso con qualsiasi numero di parametri |
Generico aperto che utilizza qualsiasi parametro del tipo |
dove T=int, U=string, V=bool |
Generico chiuso (ad esempio |
Generico aperto con un parametro |
Generico aperto che utilizza il parametro del tipo |
|
Generico aperto (ad esempio |
Se un tipo implementa più di un'interfaccia della raccolta di elenco, vengono applicate le restrizioni seguenti:
Se il tipo implementa IEnumerable 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 generico di int e IEnumerable 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 |
oppure
|
Generico chiuso |
Generico chiuso (qualsiasi numero di parametri) |
IDictionary, chiuso |
|
Generico chiuso (ad esempio |
Generico chiuso (qualsiasi numero di parametri) |
IDictionary generico, la chiave o il valore è chiuso, l'altro è aperto e utilizza uno dei parametri del tipo |
oppure
|
Generico chiuso (ad esempio |
Generico chiuso (qualsiasi numero di parametri) |
IDictionary generico, chiave e valore sono aperti e ognuno utilizza uno dei parametri del tipo |
|
Generico chiuso (ad esempio |
Generico aperto (due parametri) |
IDictionary generico, aperto, utilizza entrambi i parametri generici del tipo nell'ordine in cui sono visualizzati. |
|
Generico aperto (ad esempio |
Se il tipo implementa sia IDictionary che IDictionary generico, solo IDictionary generico viene considerato.
Il riferimento a tipi generici parziali non è supportato.
I duplicati non sono consentiti, ad esempio non è possibile aggiungere l'oggetto List 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 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 ulteriori informazioni su come viene considerata una matrice di XmlNode, vedere Tipi XML e ADO.NET nei contratti 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 |
---|---|---|
IDictionary generico |
get_Keys, get_Values |
Add generico |
IDictionary |
get_Keys, get_Values |
Add |
IList generico |
Indicizzatore IList generico |
Add generico |
ICollection generico |
Enumerator |
Add generico |
IList |
Indicizzatore IList |
Add |
IEnumerable generico |
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 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 predefinito, 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 generica di Integer che ICollection 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 predefinito 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, è https://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, Timespan e 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 di chiave e valore viene aggiunto al nome. Per ulteriori informazioni su hash di spazio dei nomi, vedere Nomi di contratto 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 Nomi di contratto dati, si applicano completamente ai tipi di raccolta, ovvero è possibile utilizzare 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:
Applicare l'attributo DataContractAttribute a un tipo al quale è stato applicato l'attributo CollectionDataContractAttribute oppure a uno dei tipi derivati.
Applicare l'attributo CollectionDataContractAttribute a un tipo che implementa l'interfaccia IXmlSerializable.
Applicare l'attributo CollectionDataContractAttribute a un tipo diverso da una raccolta.
Tentare di impostare KeyName o ValueName su un attributo CollectionDataContractAttribute applicato a un tipo diverso da un dizionario.
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> {}
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 conosciuti. È anche vero il contrario: nei casi in cui il nome non è serializzato, l'aggiunta del tipo all'elenco dei tipi conosciuti 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 conosciuti.
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.