Collezione <T> contro Elenco <T> cosa dovresti usare sulle tue interfacce?


162

Il codice è simile al seguente:

namespace Test
{
    public interface IMyClass
    {
        List<IMyClass> GetList();
    }

    public class MyClass : IMyClass
    {
        public List<IMyClass> GetList()
        {
            return new List<IMyClass>();
        }
    }
}

Quando eseguo Analisi del codice ricevo la seguente raccomandazione.

Avviso 3 CA1002: Microsoft.Design: modifica 'Elenco' in 'IMyClass.GetList ()' per utilizzare Collection, ReadOnlyCollection o KeyedCollection

Come devo risolvere questo e quali sono le buone pratiche qui?

Risposte:


234

Per rispondere alla parte "perché" della domanda sul perché no List<T>, i motivi sono a prova di futuro e semplicità API.

A prova di futuro

List<T>non è progettato per essere facilmente estensibile tramite la sottoclasse; è progettato per essere veloce per le implementazioni interne. Noterai che i metodi su di esso non sono virtuali e quindi non possono essere sovrascritti e non ci sono hook nelle sue operazioni Add/ Insert/ Remove.

Ciò significa che se in futuro è necessario modificare il comportamento della raccolta (ad es. Per rifiutare oggetti null che le persone cercano di aggiungere o per eseguire lavori aggiuntivi quando ciò accade, ad esempio l'aggiornamento dello stato della classe), è necessario modificare il tipo di raccolta che ritorni a una sottoclasse, che sarà una rottura dell'interfaccia (ovviamente cambiare la semantica di cose come non consentire null può anche essere una modifica dell'interfaccia, ma cose come l'aggiornamento del tuo stato di classe interno non lo sarebbero).

Quindi restituendo una classe che può essere facilmente sottoclassata come Collection<T>o un'interfaccia come IList<T>, ICollection<T>oppure IEnumerable<T>è possibile modificare l'implementazione interna in un tipo di raccolta diverso per soddisfare le proprie esigenze, senza violare il codice dei consumatori perché può essere comunque restituito come il tipo che si aspettano.

Semplicità API

List<T>contiene molte operazioni utili come BinarySearch, Sorte così via. Tuttavia, se questa è una raccolta che stai esponendo, è probabile che controlli la semantica dell'elenco e non i consumatori. Quindi, sebbene la tua classe internamente possa aver bisogno di queste operazioni, è molto improbabile che i consumatori della tua classe vogliano (o addirittura dovrebbero) chiamarli.

Pertanto, offrendo una classe o un'interfaccia di raccolta più semplice, riduci il numero di membri che gli utenti della tua API vedono e ne semplifica l'utilizzo.


1
Questa risposta è morta. Un'altra buona lettura sull'argomento può essere trovata qui: blogs.msdn.com/fxcop/archive/2006/04/27/…
senfo

7
Vedo i tuoi primi punti, ma non so se concordo sulla tua parte relativa alla semplicità dell'API.
Boris Callens,

stackoverflow.com/a/398988/2632991 Anche qui c'è un ottimo post su quale sia la differenza tra Collezioni ed Elenchi.
El Mac,

2
blogs.msdn.com/fxcop/archive/2006/04/27/… -> È morto. Abbiamo informazioni più recenti per questo?
Machado,


50

Dichiarerei personalmente di restituire un'interfaccia piuttosto che una raccolta concreta. Se si desidera davvero l'accesso all'elenco, utilizzare IList<T>. Altrimenti, considera ICollection<T>e IEnumerable<T>.


1
L'IList estenderebbe quindi l'interfaccia ICollection?
bovium

8
@Jon: So che è vecchio, ma puoi commentare cosa dice Krzysztof su blogs.msdn.com/b/kcwalina/archive/2005/09/26/474010.aspx ? In particolare il suo commento, We recommend using Collection<T>, ReadOnlyCollection<T>, or KeyedCollection<TKey,TItem> for outputs and properties and interfaces IEnumerable<T>, ICollection<T>, IList<T> for inputs. CA1002 sembra andare d'accordo con i commenti di Krzysztof. Non riesco a immaginare perché una collezione concreta sia raccomandata al posto di un'interfaccia e perché la distinzione tra input / output.
Nelson Rothermel,

3
@Nelson: È raro che tu voglia richiedere ai chiamanti di passare in un elenco immutabile, ma è ragionevole restituirne uno in modo che sappiano che è sicuramente immutabile. Non sono sicuro delle altre collezioni però. Sarebbe bello avere più dettagli.
Jon Skeet,

2
Non è per un caso specifico. Ovviamente in generale ReadOnlyCollection<T>non avrebbe senso per un input. Allo stesso modo, IList<T>come dice un input, "Ho bisogno di Sort () o di qualche altro membro che ha un IList" che non ha senso per un output. Ma quello che intendevo era, perché sarebbe ICollection<T>raccomandato come input e Collection<T>come output. Perché non utilizzare ICollection<T>come output anche come hai suggerito?
Nelson Rothermel,

9
Sto pensando che abbia a che fare con l'ambiguità. Collection<T>ed ReadOnlyCollection<T>entrambi derivano da ICollection<T>(cioè non c'è IReadOnlyCollection<T>). Se si restituisce l'interfaccia, non è ovvio quale sia e se possa essere modificata. Comunque, grazie per il tuo contributo. Questo è stato un buon controllo di sanità mentale per me.
Nelson Rothermel,

3

Non credo che nessuno abbia ancora risposto alla parte "perché" ... quindi ecco qui. Il motivo "perché" tu "dovresti" usare un Collection<T>invece di a List<T>è perché se esponi un List<T>, allora chiunque abbia accesso al tuo oggetto può modificare gli elementi nell'elenco. Considerando che Collection<T>dovrebbe indicare che stai creando i tuoi metodi "Aggiungi", "Rimuovi", ecc.

Probabilmente non dovrai preoccuparti, perché probabilmente stai codificando l'interfaccia solo per te (o forse alcuni colleghi). Ecco un altro esempio che potrebbe avere senso.

Se si dispone di un array pubblico, ad esempio:

public int[] MyIntegers { get; }

Lo penseresti perché c'è solo un accessor "get" che nessuno può fare confusione con i valori, ma non è vero. Chiunque può modificare i valori al suo interno in questo modo:

someObject.MyIngegers[3] = 12345;

Personalmente, userei solo List<T>nella maggior parte dei casi. Ma se stai progettando una libreria di classi che stai per distribuire a sviluppatori casuali e devi fare affidamento sullo stato degli oggetti ... allora vorrai creare la tua collezione e bloccarla da lì: )


"Se restituisci Elenco <T> al codice client, non sarai mai in grado di ricevere notifiche quando il codice client modifica la raccolta." - FX COP ... Vedi anche " msdn.microsoft.com/en-us/library/0fss9skc.aspx " ... caspita, dopo tutto non sono fuori base :)
Timothy Khouri,

Wow - anni dopo vedo che il link che ho incollato nel commento sopra aveva una citazione alla fine, quindi non ha funzionato ... msdn.microsoft.com/en-us/library/0fss9skc.aspx
Timothy Khouri,

2

Si tratta principalmente di sottrarre le proprie implementazioni invece di esporre l'oggetto List da manipolare direttamente.

Non è buona pratica lasciare che altri oggetti (o persone) modifichino direttamente lo stato dei tuoi oggetti. Pensa ai getter / setter di proprietà.

Raccolta -> Per la raccolta normale
ReadOnlyCollection -> Per le raccolte che non devono essere modificate
KeyedCollection -> Quando si desidera invece dizionari.

Come risolverlo dipende da cosa vuoi che faccia la tua classe e dallo scopo del metodo GetList (). Puoi elaborare?


1
Ma Collection <T> e KeyedCollection <,> non sono neanche di sola lettura.
nawfal,

@nawfal E dove ho detto che è?
Chakrit,

1
Si afferma di non consentire ad altri oggetti (o persone) di modificare direttamente lo stato dei propri oggetti e di elencare 3 tipi di raccolta. Escludere ReadOnlyCollectiongli altri due non obbedisce.
nawfal,

Questo si riferisce a "buone pratiche". Contesto corretto per favore. L'elenco seguente indica semplicemente i requisiti di base di tali tipi poiché l'OP vuole comprendere l'avvertimento. Poi continuo a chiedergli lo scopo di GetList()poterlo aiutare più correttamente.
Chakrit,

1
Ok bene capisco quella parte. Ma la parte del ragionamento non è ancora valida secondo me. Dici che Collection<T>aiuta a sottrarre l'implementazione interna e impedisce la manipolazione diretta dell'elenco interno. Come? Collection<T>è semplicemente un wrapper, che opera sulla stessa istanza passata. È una collezione dinamica destinata all'eredità, nient'altro (la risposta di Greg è più rilevante qui).
nawfal

1

In questo tipo di caso, di solito cerco di esporre la minima quantità di implementazione necessaria. Se i consumatori non hanno bisogno di sapere che stai effettivamente utilizzando un elenco, non è necessario restituire un elenco. Tornando come Microsoft suggerisce una raccolta, nascondi il fatto che stai utilizzando un elenco dai consumatori della tua classe e li isola da un cambiamento interno.


1

Qualcosa da aggiungere anche se è passato molto tempo da quando questo è stato chiesto.

Quando il tipo di elenco deriva da List<T>anziché anziché Collection<T>, non è possibile implementare i metodi virtuali protetti Collection<T>implementati. Ciò significa che il tipo derivato non può rispondere nel caso in cui vengano apportate modifiche all'elenco. Questo è perchéList<T> presuppone che tu sia consapevole quando aggiungi o rimuovi elementi. Essere in grado di rispondere alle notifiche è un sovraccarico e quindi List<T>non lo offre.

Nei casi in cui il codice esterno ha accesso alla tua raccolta, potresti non avere il controllo di quando un articolo viene aggiunto o rimosso. Pertanto Collection<T>fornisce un modo per sapere quando l'elenco è stato modificato.


0

Non vedo alcun problema con la restituzione di qualcosa del genere

this.InternalData.Filter(crteria).ToList();

Se ho restituito una copia disconnessa di dati interni o il risultato distaccato di una query di dati, posso restituire in modo sicuro List<TItem>senza esporre alcun dettaglio di implementazione e consentire di utilizzare i dati restituiti in modo conveniente.

Ma questo dipende dal tipo di consumatore che mi aspetto: se si tratta di una griglia di dati simile a quella che preferisco restituire, IEnumerable<TItem> che sarà comunque l'elenco di articoli copiato nella maggior parte dei casi :)


-1

Bene, la classe Collection è in realtà solo una classe wrapper attorno ad altre raccolte per nascondere i loro dettagli di implementazione e altre funzionalità. Suppongo che ciò abbia a che fare con il modello di codifica che nasconde la proprietà in linguaggi orientati agli oggetti.

Penso che non dovresti preoccuparti, ma se vuoi davvero compiacere lo strumento di analisi del codice, fai solo quanto segue:

//using System.Collections.ObjectModel;

Collection<MyClass> myCollection = new Collection<MyClass>(myList);

1
Scusa, era un errore di battitura. Collezione I menat <MyClass>. Non vedo davvero l'ora di mettere le mani su C # 4 e la covarianza generica tra l'altro!
Tamas Czinege,

Avvolgere in una Collection<T>protezione né se stessa né la collezione sottostante per quanto ho capito.
nawfal,

Quel pezzo di codice era "per favore lo strumento di analisi del codice". Non credo che @TamasCzinege abbia detto da nessuna parte che l'utilizzo Collection<T>proteggerà immediatamente la tua collezione sottostante.
Chakrit,
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.