Quando utilizzare IList e quando utilizzare Elenco


180

So che IList è l'interfaccia e List è il tipo concreto, ma non so ancora quando usarli. Quello che sto facendo ora è se non ho bisogno dei metodi Sort o FindAll che utilizzo l'interfaccia. Ho ragione? Esiste un modo migliore per decidere quando utilizzare l'interfaccia o il tipo concreto?


1
Se qualcuno si chiede ancora, trovo le risposte migliori qui: stackoverflow.com/questions/400135/listt-or-ilistt
Crismogram

Risposte:


175

Seguo due regole:

  • Accetta il tipo più semplice che funzionerà
  • Restituisce il tipo più ricco di cui l'utente avrà bisogno

Pertanto, quando si scrive una funzione o un metodo che accetta una raccolta, scriverla non per prendere un elenco, ma un IList <T>, un ICollection <T> o IEnumerable <T>. Le interfacce generiche continueranno a funzionare anche per elenchi eterogenei perché System.Object può essere anche una T. In questo modo risparmierai mal di testa se decidi di utilizzare uno Stack o qualche altra struttura di dati più avanti. Se tutto ciò che devi fare nella funzione è foreach attraverso di essa, IEnumerable <T> è davvero tutto ciò che dovresti chiedere.

D'altra parte, quando si restituisce un oggetto da una funzione, si desidera offrire all'utente il set più completo possibile di operazioni senza che debbano eseguire il cast. Quindi, in tal caso, se si tratta di un Elenco <T> internamente, restituire una copia come Elenco <T>.


43
Non dovresti trattare i tipi di input / output in modo diverso. I tipi di input e output dovrebbero essere entrambi il tipo più semplice (preferibilmente interfaccia) che supporterà le esigenze dei clienti. L'incapsulamento si basa sul dire ai clienti il ​​meno possibile sull'implementazione della tua classe. Se si restituisce un elenco concreto, non è possibile passare a un altro tipo migliore senza forzare tutti i client a ricompilare / aggiornare.
Ash,

11
Non sono d'accordo con le 2 regole ... Userei il tipo più primitivo e in particolare quando tornerò in questo caso IList (meglio IEnumarable) e dovresti lavorare con List nella tua funzione interna. Quindi, quando hai bisogno di "aggiungere" o "ordina", usa Raccolta se ne hai bisogno, quindi usa Elenco. Quindi la mia dura regola sarebbe: INIZIA sempre con IENumarable e se hai bisogno di più estendi ...
ethem

2
Per tua comodità, le "due regole" hanno un nome: principio di robustezza (noto anche come legge di Postel) .
easoncxz,

Qualunque sia il lato del dibattito su cui qualcuno sta discutendo se restituire il tipo più semplice o il tipo più ricco, qualcosa da considerare è che quando si restituisce un'interfaccia molto semplificata, il codice che consuma può spesso - sebbene non sempre - usare una if...elsecatena con la isparola chiave per capire ne esce un tipo molto più ricco e finisce per lanciarlo e usarlo comunque. Quindi non ti viene necessariamente garantito di nascondere qualcosa utilizzando un'interfaccia di base, invece di oscurarla. Tuttavia, renderlo più difficile può anche far riflettere due volte lo scrittore del codice di consumo su come lo stanno usando.
Panzercrisis,

6
Non sono molto d'accordo sul punto n. 2, specialmente se questo è al limite del servizio / API. La restituzione di raccolte modificabili può dare l'impressione che le raccolte siano "attive" e che i metodi di chiamata simili Add()e Remove()possano avere effetti al di là della sola raccolta. Restituire un'interfaccia di sola lettura come IEnumerablespesso è la strada da percorrere per i metodi di recupero dei dati. Il tuo consumatore può proiettarlo in un tipo più ricco secondo necessità.
STW,

56

Le linee guida Microsoft controllate da FxCop scoraggiano l'uso dell'elenco <T> nelle API pubbliche - preferisci IList <T>.

Per inciso, ora quasi sempre dichiaro matrici monodimensionali come IList <T>, il che significa che posso usare costantemente la proprietà IList <T> .Count anziché Array.Length. Per esempio:

public interface IMyApi
{
    IList<int> GetReadOnlyValues();
}

public class MyApiImplementation : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        List<int> myList = new List<int>();
        ... populate list
        return myList.AsReadOnly();
    }
}
public class MyMockApiImplementationForUnitTests : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        IList<int> testValues = new int[] { 1, 2, 3 };
        return testValues;
    }
}

3
Mi piace di più questa spiegazione / esempio!
JonH,

28

C'è una cosa importante che le persone sembrano sempre trascurare:

È possibile passare un array semplice a qualcosa che accetta un IList<T>parametro, quindi è possibile chiamare IList.Add()e ricevere un'eccezione di runtime:

Unhandled Exception: System.NotSupportedException: Collection was of a fixed size.

Ad esempio, considera il seguente codice:

private void test(IList<int> list)
{
    list.Add(1);
}

Se lo chiami come segue, otterrai un'eccezione di runtime:

int[] array = new int[0];
test(array);

Ciò accade perché l'utilizzo di matrici semplici con IList<T>violazione del principio di sostituzione di Liskov.

Per questo motivo, se stai chiamando IList<T>.Add()potresti prendere in considerazione l'idea di richiedere un List<T>invece di un IList<T>.


Questo è banalmente vero per ogni interfaccia. Se vuoi dare seguito al tuo argomento, potresti argomentare di non usare mai alcuna interfaccia, perché potrebbe implementare una sua implementazione. Se, al contrario, considera la suggestione data dalla OP a preferire List<T>sopra IList<T>, si dovrebbe anche essere a conoscenza dei motivi per cui IList<T>è raccomandato. (Ad esempio blogs.msdn.microsoft.com/kcwalina/2005/09/26/… )
Micha Wiedenmann,

3
@MichaWiedenmann La mia risposta qui è specifica per quando chiami IList<T>.Add(). Non sto dicendo che non dovresti usare IList<T>- sto solo indicando un possibile trabocchetto. (Io tendo ad usare IEnumerable<T>o IReadOnlyList<T>oppure IReadOnlyCollection<T>a preferenza di IList<T>se posso.)
Matthew Watson

24

Concordo con il consiglio di Lee per prendere parametri, ma non tornare.

Se specifichi i tuoi metodi per restituire un'interfaccia, ciò significa che sei libero di modificare l'esatta implementazione in un secondo momento senza che il metodo di consumo lo sappia mai. Ho pensato che non avrei mai dovuto cambiare da un Elenco <T>, ma in seguito ho dovuto cambiare per utilizzare una libreria di elenchi personalizzati per le funzionalità extra fornite. Perché avevo restituito solo un IList <T> nessuna delle persone che utilizzavano la libreria doveva cambiare il proprio codice.

Ovviamente ciò deve applicarsi solo ai metodi che sono visibili esternamente (cioè metodi pubblici). Personalmente utilizzo le interfacce anche nel codice interno, ma poiché sei in grado di modificare tutto il codice da solo se apporti modifiche non è strettamente necessario.


22

IEnumerable
Dovresti provare ad usare il tipo meno specifico adatto al tuo scopo.
IEnumerableè meno specifico di IList.
Si utilizza IEnumerablequando si desidera scorrere gli elementi in una raccolta.

Ilist
IList implementa IEnumerable.
Dovresti usare IListquando hai bisogno di accedere per indice alla tua collezione, aggiungere ed eliminare elementi, ecc ...

Elenco
List strumenti IList.


3
Risposta eccellente e chiara, che ho contrassegnato come utile. Tuttavia, aggiungerei che per la maggior parte degli sviluppatori, la maggior parte delle volte, la piccola differenza nelle dimensioni e nelle prestazioni del programma non merita di essere preoccupata: in caso di dubbio, basta usare un Elenco.
Graham Laight,

9

È sempre meglio utilizzare il tipo di base più basso possibile. Ciò offre all'implementatore della tua interfaccia, o utente del tuo metodo, l'opportunità di utilizzare ciò che preferisce dietro le quinte.

Per le raccolte dovresti mirare a usare IEnumerable ove possibile. Questo offre la massima flessibilità ma non è sempre adatto.


1
È sempre meglio accettare il tipo di base più basso possibile. Il ritorno è una storia diversa. Scegli quali opzioni potrebbero essere utili. Quindi pensi che il tuo cliente potrebbe voler utilizzare l'accesso indicizzato? Impedisci loro di ToList()inviare il tuo reso IEnumerable<T>che era già un elenco e restituisci IList<T>invece un . Ora, i clienti possono beneficiare di ciò che è possibile fornire senza sforzo.
Timo,

5

Se stai lavorando all'interno di un singolo metodo (o anche in una singola classe o assembly in alcuni casi) e nessuno al di fuori vedrà quello che stai facendo, usa la pienezza di un Elenco. Ma se stai interagendo con un codice esterno, come quando stai restituendo un elenco da un metodo, allora vuoi solo dichiarare l'interfaccia senza necessariamente legarti a un'implementazione specifica, specialmente se non hai controllo su chi compila contro il tuo codice in seguito. Se hai iniziato con un tipo concreto e hai deciso di passare a un altro, anche se utilizza la stessa interfaccia, infrangerai il codice di qualcun altro a meno che non abbia iniziato con un'interfaccia o un tipo di base astratto.


4

Non penso che ci siano regole dure e veloci per questo tipo di cose, ma di solito seguo le linee guida per usare il modo più leggero possibile fino a quando non è assolutamente necessario.

Ad esempio, supponiamo che tu abbia una Personclasse e una Groupclasse. Un Groupesempio ha molte persone, in modo da un elenco qui avrebbe senso. Quando dichiaro l'oggetto list in Groupuserò un IList<Person>e lo creerò come a List.

public class Group {
  private IList<Person> people;

  public Group() {
    this.people = new List<Person>();
  }
}

E, se non hai nemmeno bisogno di tutto IList, puoi sempre usarlo IEnumerableanche. Con i moderni compilatori e processori, non penso che ci sia davvero alcuna differenza di velocità, quindi questa è più solo una questione di stile.


3
perché non renderlo solo un elenco in primo luogo? Ancora non capisco perché il bonus si ottiene dal renderlo un IList, quindi nel costruttore lo si fa in un Elenco <>
chobo2

Sono d'accordo, se stai creando esplicitamente un oggetto List <T>, perdi il vantaggio dell'interfaccia?
The_Butcher,

4

Molto spesso è meglio utilizzare il tipo utilizzabile più generale, in questo caso l'IList o anche meglio l'interfaccia IEnumerable, in modo da poter passare comodamente l'implementazione in un secondo momento.

Tuttavia, in .NET 2.0, c'è una cosa fastidiosa: IList non ha un metodo Sort () . È invece possibile utilizzare un adattatore in dotazione:

ArrayList.Adapter(list).Sort()

2

Dovresti usare l'interfaccia solo se ne hai bisogno, ad esempio, se il tuo elenco viene trasmesso a un'implementazione di IList diversa da Elenco. Ciò vale quando, ad esempio, si utilizza NHibernate, che esegue il lancio di IList in un oggetto bag NHibernate durante il recupero dei dati.

Se Elenco è l'unica implementazione che utilizzerai mai per una determinata raccolta, sentiti libero di dichiararla come un'implementazione concreta di Elenco.


1

In situazioni in cui mi imbatto di solito, raramente utilizzo direttamente IList.

Di solito lo uso solo come argomento di un metodo

void ProcessArrayData(IList almostAnyTypeOfArray)
{
    // Do some stuff with the IList array
}

Ciò mi consentirà di eseguire elaborazioni generiche su quasi tutti gli array nel framework .NET, a meno che non utilizzi IEnumerable e non IList, che a volte accade.

Dipende davvero dal tipo di funzionalità di cui hai bisogno. Suggerirei di utilizzare la classe List nella maggior parte dei casi. IList è la soluzione migliore quando devi creare un array personalizzato che potrebbe avere alcune regole molto specifiche che vorresti incapsulare in una raccolta in modo da non ripetere te stesso, ma vuoi comunque che .NET lo riconosca come un elenco.


1

L'oggetto AList ti consente di creare un elenco, aggiungere elementi, rimuoverlo, aggiornarlo, indicizzarlo e così via. L'elenco viene utilizzato ogni volta che desideri un elenco generico in cui specifichi il tipo di oggetto in esso e il gioco è fatto.

D'altra parte IList è un'interfaccia. Fondamentalmente, se si desidera creare il proprio tipo di Elenco, pronunciare una classe elenco denominata Elenco libri, è possibile utilizzare l'interfaccia per fornire metodi e struttura di base alla nuova classe. IList è per quando si desidera creare la propria sottoclasse speciale che implementa Elenco.

Un'altra differenza è: IList è un'interfaccia e non può essere istanziato. L'elenco è una classe e può essere istanziato. Significa:

IList<string> MyList = new IList<string>();

List<string> MyList = new List<string>
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.