Linee guida per le raccolte

Nota

Questo contenuto è ristampato con l'autorizzazione di Pearson Education, Inc. da Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition. Tale edizione è stata pubblicata nel 2008 e il libro è stato interamente revisionato nella terza edizione. Alcune delle informazioni contenute in questa pagina potrebbero non essere aggiornate.

Qualsiasi tipo progettato specificamente per modificare un gruppo di oggetti con alcune caratteristiche comuni può essere considerato una raccolta. È quasi sempre appropriato implementare IEnumerable o IEnumerable<T>, quindi in questa sezione si considerano solo i tipi che implementano una o entrambe le interfacce come raccolte.

❌ NON usare raccolte tipate in modo debole nelle API pubbliche.

Il tipo di tutti i valori restituiti e i parametri che rappresentano gli elementi della raccolta deve essere il tipo di elemento esatto, non uno dei relativi tipi di base (questo vale solo per i membri pubblici della raccolta).

❌ NON usare ArrayList o List<T> nelle API pubbliche.

Questi tipi sono strutture di dati progettate per essere usate nell'implementazione interna, non nelle API pubbliche. List<T> è ottimizzato per prestazioni e potenza a costo della pulizia delle API e della flessibilità. Ad esempio, se si restituisce List<T>, non sarà mai possibile ricevere notifiche quando il codice client modifica la raccolta. Inoltre, List<T> espone molti membri, ad esempio BinarySearch, che non sono utili o applicabili in molti scenari. Le due sezioni seguenti descrivono i tipi (astrazioni) progettati specificamente per l'uso nelle API pubbliche.

❌ NON usare Hashtable o Dictionary<TKey,TValue> nelle API pubbliche.

Questi tipi sono strutture di dati progettate per essere usate nell'implementazione interna. Le API pubbliche devono usare IDictionary, IDictionary <TKey, TValue> o un tipo personalizzato che implementa una o entrambe le interfacce.

❌ NON usare IEnumerator<T>, IEnumerator o qualsiasi altro tipo che implementa una di queste interfacce, ad eccezione del tipo restituito di un metodo GetEnumerator.

I tipi che restituiscono enumeratori da metodi diversi da GetEnumerator non possono essere utilizzati con l'istruzione foreach.

❌ NON implementare sia IEnumerator<T> che IEnumerable<T> nello stesso tipo. Lo stesso vale per le interfacce non generiche IEnumerator e IEnumerable.

Parametri della raccolta

✔️ USARE il tipo meno specializzato possibile come tipo di parametro. La maggior parte dei membri che accetta raccolte come parametri utilizza l'interfaccia IEnumerable<T>.

❌ EVITARE di usare ICollection<T> o ICollection come parametro solo per accedere alla proprietà Count.

È invece consigliabile usare IEnumerable<T> o IEnumerable e controllare in modo dinamico se l'oggetto implementa ICollection<T> o ICollection.

Proprietà della raccolta e valori restituiti

❌ NON specificare le proprietà della raccolta impostabili.

Gli utenti possono sostituire il contenuto della raccolta cancellando prima la raccolta e quindi aggiungendo il nuovo contenuto. Se la sostituzione dell'intera raccolta è uno scenario comune, è consigliabile fornire il metodo AddRange nella raccolta.

✔️ USARE Collection<T> o una sottoclasse di Collection<T> per le proprietà o i valori restituiti che rappresentano raccolte di lettura/scrittura.

Se Collection<T> non soddisfa alcuni requisiti (ad esempio, la raccolta non deve implementare IList), usare una raccolta personalizzata implementando IEnumerable<T>, ICollection<T> o IList<T>.

✔️ USARE ReadOnlyCollection<T>, una sottoclasse di ReadOnlyCollection<T>, o in rari casi IEnumerable<T>, per proprietà o valori restituiti che rappresentano raccolte di sola lettura.

In generale, preferire ReadOnlyCollection<T>. Se non soddisfa alcuni requisiti (ad esempio, la raccolta non deve implementare IList), usare una raccolta personalizzata implementando IEnumerable<T>, ICollection<T> o IList<T>. Se si implementa una raccolta di sola lettura personalizzata, implementare ICollection<T>.IsReadOnly per restituire true.

Nei casi in cui si è certi che l'unico scenario che si vuole supportare sia l'iterazione forward-only, è sufficiente usare IEnumerable<T>.

✔️PRENDERE IN CONSIDERAZIONE di usare sottoclassi di raccolte di base generiche anziché usare direttamente le raccolte.

Ciò consente un nome migliore e l'aggiunta di membri helper non presenti nei tipi di raccolta di base. Ciò è particolarmente applicabile alle API di alto livello.

✔️ PRENDERE IN CONSIDERAZIONE la restituzione di una sottoclasse di Collection<T> o ReadOnlyCollection<T> da metodi e proprietà di uso molto comune.

In questo modo sarà possibile aggiungere metodi helper o modificare l'implementazione della raccolta in futuro.

✔️ PRENDERE IN CONSIDERAZIONE l'uso di una raccolta con chiave se gli elementi archiviati nella raccolta hanno chiavi univoche (nomi, ID e così via). Le raccolte con chiave sono raccolte che possono essere indicizzate da un numero intero e da una chiave e in genere vengono implementate ereditando da KeyedCollection<TKey,TItem>.

Le raccolte con chiavi hanno in genere footprint di memoria maggiori e non devono essere usate se il sovraccarico della memoria supera i vantaggi derivanti dalla presenza delle chiavi.

❌ NON restituisce valori Null dalle proprietà della raccolta o dai metodi che restituiscono raccolte. Restituisce invece una raccolta vuota o una matrice vuota.

La regola generale è che le raccolte o le matrici null e vuote (0 elementi) devono essere considerate uguali.

Snapshot e raccolte attive

Le raccolte che rappresentano uno stato in un determinato momento sono denominate raccolte snapshot. Ad esempio, una raccolta contenente righe restituite da una query di database sarebbe uno snapshot. Le raccolte che rappresentano sempre lo stato corrente vengono chiamate raccolte attive. Ad esempio, una raccolta di elementi ComboBox è una raccolta dinamica.

❌ NON restituire raccolte di snapshot dalle proprietà. Le proprietà devono restituire raccolte attive.

I getter delle proprietà devono essere operazioni molto leggere. La restituzione di uno snapshot richiede la creazione di una copia di una raccolta interna in un'operazione O(n).

✔️ USARE una raccolta snapshot o IEnumerable<T> dinamica (o il relativo sottotipo) per rappresentare le raccolte volatili (ad esempio, che possono cambiare senza modificare in modo esplicito la raccolta).

In generale, tutte le raccolte che rappresentano una risorsa condivisa (ad esempio, i file in una directory) sono volatili. Tali raccolte sono molto difficili o impossibili da implementare come raccolte attive, a meno che l'implementazione non sia semplicemente un enumeratore forward-only.

Scelta tra matrici e raccolte

✔️ PREFERIRE le raccolte rispetto alle matrici.

Le raccolte offrono un maggiore controllo sul contenuto, possono evolversi nel tempo e sono più utilizzabili. Inoltre, l'uso di matrici per scenari di sola lettura è sconsigliato perché il costo della clonazione della matrice è proibitivo. Studi sull'usabilità hanno dimostrato che alcuni sviluppatori si sentono più a proprio agio usando le API basate su raccolte.

Tuttavia, se si sviluppano API di basso livello, potrebbe essere preferibile usare matrici per scenari di lettura/scrittura. Le matrici hanno un footprint della memoria inferiore, che consente di ridurre il working set e l'accesso agli elementi in una matrice è più veloce perché è ottimizzato dal runtime.

✔️ VALUTARE l'uso di matrici in API di basso livello per ridurre al minimo il consumo di memoria e ottimizzare le prestazioni.

✔️ USARE matrici di byte anziché raccolte di byte.

❌ NON usare matrici per le proprietà se la proprietà deve restituire una nuova matrice (ad esempio, una copia di una matrice interna) ogni volta che viene chiamato il getter della proprietà.

Implementazione di raccolte personalizzate

✔️ PRENDERE IN CONSIDERAZIONE l'ereditarietà da Collection<T>, ReadOnlyCollection<T> o KeyedCollection<TKey,TItem> quando si progettano nuove raccolte.

✔️ IMPLEMENTARE IEnumerable<T> quando si progettano nuove raccolte. Prendere in considerazione l'implementazione di ICollection<T> o anche IList<T> laddove ha senso.

Quando si implementa tale raccolta personalizzata, seguire il modello API stabilito da Collection<T> e ReadOnlyCollection<T> il più vicino possibile. Ovvero, implementare gli stessi membri in modo esplicito, denominare i parametri come li denominano queste due raccolte e così via.

✔️ PRENDERE IN CONSIDERAZIONE l'implementazione di interfacce di raccolta non generiche (IList e ICollection) se la raccolta viene spesso passata alle API che accettano queste interfacce come input.

❌ EVITARE di implementare interfacce di raccolta su tipi con API complesse non correlate al concetto di una raccolta.

❌ NON ereditare da raccolte di base non generiche, ad esempio CollectionBase. In alternativa, usare Collection<T>, ReadOnlyCollection<T> e KeyedCollection<TKey,TItem>.

Denominazione di raccolte personalizzate

Le raccolte (tipi che implementano IEnumerable) vengono create principalmente per due motivi: (1) per creare una nuova struttura di dati con operazioni specifiche della struttura e spesso caratteristiche di prestazioni diverse rispetto alle strutture di dati esistenti (ad esempio List<T>, LinkedList<T>, Stack<T>), e (2) per creare una raccolta specializzata per contenere un set specifico di elementi (ad esempio, StringCollection). Le strutture di dati vengono spesso usate nell'implementazione interna di applicazioni e librerie. Le raccolte specializzate devono essere esposte principalmente nelle API (come tipi di proprietà e di parametro).

✔️ USARE il suffisso "Dictionary" nei nomi delle astrazioni che implementano IDictionary o IDictionary<TKey,TValue>.

✔️ USARE il suffisso "Collection" nei nomi dei tipi che implementano IEnumerable (o uno dei relativi discendenti) e rappresentano un elenco di elementi.

✔️ USARE il nome della struttura di dati appropriato per le strutture di dati personalizzate.

❌ EVITARE di usare suffissi che implicano un'implementazione specifica, ad esempio "LinkedList" o "Hashtable", nei nomi delle astrazioni di raccolta.

✔️ CONSIDERARE di aggiungere ai nomi delle raccolte il prefisso con il nome del tipo di elemento. Ad esempio, una raccolta che archivia elementi di tipo Address (implementazione di IEnumerable<Address>) deve essere denominata AddressCollection. Se il tipo di elemento è un'interfaccia, è possibile omettere il prefisso "I" del tipo di elemento. Pertanto, una raccolta di elementi IDisposable può essere chiamata DisposableCollection.

✔️ CONSIDERARE di usare il prefisso "ReadOnly" nei nomi delle raccolte di sola lettura se una raccolta scrivibile corrispondente potrebbe essere aggiunta o esiste già nel framework.

Ad esempio, una raccolta di stringhe di sola lettura deve essere chiamata ReadOnlyStringCollection.

Parti protette da copyright © 2005, 2009 Microsoft Corporation. Tutti i diritti sono riservati.

Ristampato con l'autorizzazione di Pearson Education, Inc. da Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2a edizione di Krzysztof Cwalina and Brad Abrams, pubblicato il 22 ottobre 2008 da Addison-Wesley Professional nella collana Microsoft Windows Development Series.

Vedi anche