Restituzione di "IList", "ICollection" e "Collection"


119

Sono confuso su quale tipo di raccolta dovrei restituire dai miei metodi e proprietà API pubbliche.

Le collezioni che ho in mente sono IList, ICollectione Collection.

Restituire uno di questi tipi è sempre preferito rispetto agli altri o dipende dalla situazione specifica?



Risposte:


71

In genere si dovrebbe restituire un tipo che sia il più generale possibile, cioè uno che conosce quanto basta dei dati restituiti che il consumatore deve usare. In questo modo hai maggiore libertà di modificare l'implementazione dell'API, senza rompere il codice che la sta utilizzando.

Considera anche l' IEnumerable<T>interfaccia come tipo di ritorno. Se il risultato verrà solo iterato, il consumatore non ha bisogno di altro.


25
Ma considera anche ICollection <T> che estende IEnumerable <T> per fornire utilità aggiuntive, inclusa una proprietà Count. Ciò può essere utile perché è possibile determinare la lunghezza della sequenza senza attraversare la sequenza più volte.
retrodrone

11
@retrodrone: in realtà l'implementazione del Countmetodo controlla il tipo effettivo della raccolta, quindi utilizzerà le proprietà Lengtho Countper alcuni tipi noti come gli array e ICollectionanche quando lo fornisci come file IEnumerable.
Guffa

8
@Guffa: può essere utile notare che se una classe Thing<T>implementa IList<T>ma non quella non generica ICollection, la chiamata IEnumerable<Cat>.Count()a Thing<Cat>sarebbe veloce, ma la chiamata IEnumerable<Animal>.Count()sarebbe lenta (poiché il metodo di estensione cercherebbe, e non troverebbe, un'implementazione di ICollection<Cat>). Se la classe implementasse il non generico ICollection, tuttavia, IEnumerable<Animal>.Countlo troverebbe e lo userebbe.
supercat


8
Non sono d'accordo. È meglio restituire il tipo più ricco che hai in modo che il cliente possa usarlo direttamente se è quello che vuole. Se hai un List <>, perché restituire un IEnuerable <> solo per forzare il chiamante a chiamare ToList () inutilmente?
zumalifeguard

140

ICollection<T>è un'interfaccia che espone semantica raccolta come Add(), Remove()eCount .

Collection<T> è un'implementazione concreta di ICollection<T> dell'interfaccia.

IList<T> è essenzialmente un ICollection<T> con accesso basato sull'ordine casuale.

In questo caso dovresti decidere se i tuoi risultati richiedono o meno la semantica della lista come l'indicizzazione basata sull'ordine (quindi usa IList<T>) o se devi solo restituire una "borsa" non ordinata di risultati (quindi usa ICollection<T>).


5
Collection<T>attrezzi IList<T>e non solo ICollection<T>.
CodesInChaos

56
Tutte le collezioni in .Net sono ordinate, perché IEnumerable<T>è ordinato. Ciò che distingue IList<T>da ICollection<T>è che offre ai membri di lavorare con gli indici. Ad esempio list[5], funziona, ma collection[5]non verrà compilato.
svick

5
Non sono inoltre d'accordo sul fatto che le raccolte ordinate dovrebbero essere implementate IList<T>. Ci sono molte collezioni ordinate che non lo fanno. IList<T>riguarda l'accesso rapido indicizzato.
CodesInChaos

4
@yoyo Le classi e le interfacce della raccolta .net sono semplicemente progettate male e nominate male. Non penserei troppo al motivo per cui li chiamano così.
CodesInChaos

6
@svick: tutte le raccolte sono ordinate perché IEnumerable<T>sono state ordinate? Prendi HashSet<T>: implementa IEnumerable<T>ma chiaramente non è ordinato. Cioè non hai alcuna influenza sull'ordine degli elementi e questo ordine potrebbe cambiare in qualsiasi momento, se la tabella hash interna viene riorganizzata.
Olivier Jacot-Descombes

61

La differenza principale tra IList<T>e ICollection<T>è che IList<T>ti consente di accedere agli elementi tramite un indice. IList<T>descrive i tipi simili ad array. ICollection<T>È possibile accedere agli elementi in un solo tramite enumerazione. Entrambi consentono l'inserimento e la cancellazione di elementi.

Se hai solo bisogno di enumerare una raccolta, IEnumerable<T>è preferibile. Ha due vantaggi rispetto agli altri:

  1. Non consente modifiche alla raccolta (ma non agli elementi, se sono di tipo riferimento).

  2. Consente la più ampia varietà possibile di fonti, comprese le enumerazioni generate algoritmicamente e che non sono affatto raccolte.

Collection<T>è una classe base che è utile principalmente agli implementatori di raccolte. Se lo esponi nelle interfacce (API), molte raccolte utili non derivanti da esso verranno escluse.


Uno svantaggio IList<T>è che gli array lo implementano ma non consentono di aggiungere o rimuovere elementi (cioè non è possibile modificare la lunghezza dell'array). Verrà generata un'eccezione se si chiama IList<T>.Add(item)un array. La situazione è in qualche modo disinnescata poiché IList<T>ha una proprietà booleana IsReadOnlyche puoi controllare prima di tentare di farlo. Ma ai miei occhi, questo è ancora un difetto di progettazione nella libreria . Pertanto, utilizzo List<T>direttamente quando è richiesta la possibilità di aggiungere o rimuovere elementi.


Analisi del codice (e FxCop) consiglia che quando la restituzione di un insieme generico di cemento, deve essere restituito uno dei seguenti: Collection, ReadOnlyCollectiono KeyedCollection. E quello Listnon dovrebbe essere restituito.
DavidRR

@DavidRR: spesso è utile passare un elenco a un metodo che deve aggiungere o rimuovere elementi. Se si digita il parametro come IList<T>, non è possibile garantire che il metodo non venga chiamato con un array come parametro.
Olivier Jacot-Descombes

7

IList<T>è l'interfaccia di base per tutti gli elenchi generici. Trattandosi di una raccolta ordinata, l'implementazione può decidere l'ordinamento, che va dall'ordine ordinato all'ordine di inserzione. Inoltre Ilistha la proprietà Item che consente ai metodi di leggere e modificare le voci nell'elenco in base al loro indice. Ciò rende possibile inserire, rimuovere un valore nella / dalla lista in corrispondenza di un indice di posizione.

Inoltre IList<T> : ICollection<T>, tutti i metodi di ICollection<T>sono disponibili anche qui per l'implementazione.

ICollection<T>è l'interfaccia di base per tutte le raccolte generiche. Definisce dimensioni, enumeratori e metodi di sincronizzazione. È possibile aggiungere o rimuovere un elemento in una raccolta ma non è possibile scegliere in quale posizione si trova a causa dell'assenza della proprietà index.

Collection<T>fornisce un'implementazione per IList<T>, IListe IReadOnlyList<T>.

Se utilizzi un tipo di interfaccia più ristretto come ICollection<T>invece di IList<T>, proteggi il tuo codice da modifiche di rilievo. Se utilizzi un tipo di interfaccia più ampio, ad esempio IList<T>, sei maggiormente a rischio di interrompere le modifiche al codice.

Citando da una fonte ,

ICollection, ICollection<T>: Vuoi modificare la collezione o ti interessa la sua dimensione. IList, IList<T>: Vuoi modificare la collezione e ti preoccupi dell'ordine e / o del posizionamento degli elementi nella collezione.


5

La restituzione di un tipo di interfaccia è più generale, quindi (mancando ulteriori informazioni sul tuo caso d'uso specifico) mi appoggerei a quello. Se vuoi esporre il supporto all'indicizzazione, scegli IList<T>, altrimenti ICollection<T>sarà sufficiente. Infine, se vuoi indicare che i tipi restituiti sono di sola lettura, scegli IEnumerable<T>.

E, nel caso non l'avessi letto prima, Brad Abrams e Krzysztof Cwalina hanno scritto un ottimo libro intitolato "Linee guida per la progettazione del framework: convenzioni, idiomi e modelli per le librerie .NET riutilizzabili" (puoi scaricare un riassunto da qui ).


IEnumerable<T>è di sola lettura? Certo, ma quando si risintonizza in un contesto di repository, anche dopo l'esecuzione di ToList non è thread-safe. Il problema è che quando l'origine dati originale ottiene GC, IEnumarble.ToList () non funziona in un contesto con più thread. Funziona su uno lineare perché GC viene chiamato dopo aver fatto il lavoro, ma usare asyncora un giorno in 4.5+ IEnumarbale sta diventando un dolore alla palla.
Piotr Kula

-3

Ci sono alcuni argomenti che derivano da questa domanda:

  • interfacce contro classi
  • quale classe specifica, da diverse classi, raccolte, elenchi, array simili?
  • Classi comuni e raccolte di elementi secondari ("generici")

Potresti sottolineare che è un'API orientata agli oggetti

interfacce contro classi

Se non hai molta esperienza con le interfacce, ti consiglio di attenersi alle lezioni. Vedo molte volte che gli sviluppatori saltano alle interfacce, anche se non è necessario.

E, finisci con un design di interfaccia scadente, invece di un buon design di classe, che, a proposito, può alla fine essere migrato a un buon design di interfaccia ...

Vedrai molte interfacce nell'API, ma non affrettarti, se non ne hai bisogno.

Alla fine imparerai come applicare le interfacce al tuo codice.

quale classe specifica, da diverse classi, raccolte, elenchi, array simili?

Esistono diverse classi in c # (dotnet) che possono essere scambiate. Come già accennato, se hai bisogno di qualcosa da una classe più specifica, come "CanBeSortedClass", rendilo esplicito nella tua API.

Il tuo utente API ha davvero bisogno di sapere che la tua classe può essere ordinata o applicare un formato agli elementi? Quindi usa "CanBeSortedClass" o "ElementsCanBePaintedClass", altrimenti usa "GenericBrandClass".

Altrimenti, usa una classe più generale.

Classi di raccolte comuni e raccolte di elementi secondari ("generici")

Scoprirai che esistono classi che contengono altri elementi e puoi specificare che tutti gli elementi devono essere di un tipo specifico.

Le raccolte generiche sono quelle classi che è possibile utilizzare la stessa raccolta, per diverse applicazioni di codice, senza dover creare una nuova raccolta, per ogni nuovo tipo di elemento secondario, come questa: Raccolta .

Il tuo utente API avrà bisogno di un tipo molto specifico, uguale per tutti gli elementi?

Usa qualcosa di simile List<WashingtonApple>.

Il tuo utente API avrà bisogno di diversi tipi correlati?

Esporre List<Fruit>per la vostra API, e utilizzare List<Orange> List<Banana>, List<Strawberry>internamente, dove Orange, Bananae Strawberrysono discendenti da Fruit.

Il tuo utente API avrà bisogno di una raccolta di tipi generici?

Usa List, dove sono tutti gli articoli object.

Saluti.


7
"Se non hai molta esperienza con le interfacce, ti consiglio di seguire le lezioni" Questo non è un buon consiglio. È come dire che se c'è qualcosa che non sai, stai alla larga. Le interfacce sono estremamente potenti e supportano il concetto di "Programma per astrazioni contro concrezioni". Sono anche abbastanza potenti per eseguire test unitari a causa della capacità di scambiare tipi tramite mock. Ci sono un sacco di altri ottimi motivi per usare le interfacce oltre a questi commenti, quindi per favore esplorali con tutti i mezzi.
atconway

@atconway Uso regolarmente le interfacce, ma, come molti concetti, è uno strumento potente, può essere facile da usare in modo misto. E, a volte, lo sviluppatore potrebbe non averne bisogno. Il mio suggerimento non era "non interfacce mai", il mio suggerimento era "in questo caso, puoi saltare le interfacce. Impara a conoscere e potresti ottenere il meglio da esse in seguito". Saluti.
umlcat

1
Consiglio sempre di programmare su un'interfaccia. Aiuta a mantenere il codice liberamente accoppiato.
mac10688

@ mac10688 La mia risposta precedente (atconway) si applica anche al tuo commento.
umlcat
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.