Elenco o IList

Qualcuno può spiegarmi perché vorrei usare IList su List in C #?

Domanda correlata: Perché è considerato negativo esporre l’ List

Se stai esponendo la tua class attraverso una libreria che gli altri useranno, generalmente la vuoi esporre tramite interfacce piuttosto che implementazioni concrete. Ciò sarà di aiuto se deciderai di cambiare l’implementazione della tua class in seguito per utilizzare una class concreta diversa. In tal caso, gli utenti della tua biblioteca non dovranno aggiornare il loro codice poiché l’interfaccia non cambia.

Se lo stai usando internamente, potresti non interessarti troppo e usare List potrebbe essere ok.

La risposta meno popolare è che i programmatori amano far finta che il loro software venga riutilizzato in tutto il mondo, quando infatti la maggior parte dei progetti sarà gestita da una piccola quantità di persone e comunque i buoni audio relativi all’interfaccia sono, stai illudendo te stesso.

Astronauti di architettura . Le probabilità che tu possa mai scrivere il tuo IList che aggiunge qualcosa a quelli già presenti nel framework .NET sono così remoti che è una medusa teoretica riservata alle “best practice”.

Astronauti del software

Ovviamente se ti viene chiesto quale uso usi in un’intervista, dici IList, sorridi, ed entrambi ti guardano compiaciuti di essere così intelligenti. O per un’API pubblica, IList. Spero che tu capisca il mio punto.

Interface è una promise (o un contratto).

Come sempre è con le promesse – più piccole sono le migliori .

Alcuni dicono “usa sempre IList invece di List “.
Vogliono che tu cambi le firme dei metodi da void Foo(List input) a void Foo(IList input) .

Queste persone hanno torto.

È più sfumato di così. Se stai restituendo un IList come parte dell’interfaccia pubblica alla tua libreria, ti lasci delle opzioni interessanti per magari creare un elenco personalizzato in futuro. Potresti non averne mai bisogno, ma è un argomento. Penso che sia l’intero argomento per la restituzione dell’interfaccia invece del tipo concreto. Vale la pena menzionare, ma in questo caso ha un grave difetto.

Come controprogetto minore, è ansible che ogni singolo chiamante abbia bisogno di un List e che il codice chiamante sia pieno di .ToList()

Ma, cosa ancora più importante, se si accetta un parametro IList come parametro si dovrebbe fare attenzione, perché IList e List non si comportano allo stesso modo. Nonostante la somiglianza nel nome, e nonostante condividano un’interfaccia , non espongono lo stesso contratto .

Supponiamo di avere questo metodo:

 public Foo(List a) { a.Add(someNumber); } 

Un utile collega “rifatta” il metodo per accettare IList .

Il tuo codice ora è rotto, perché int[] implementa IList , ma ha dimensioni fisse. Il contratto per ICollection (la base di IList ) richiede il codice che lo utilizza per controllare la flag IsReadOnly prima di tentare di aggiungere o rimuovere elementi dalla raccolta. Il contratto per la List non lo fa.

Il Principio di sostituzione di Liskov (semplificato) afferma che un tipo derivato dovrebbe poter essere usato al posto di un tipo di base, senza precondizioni o post-condizioni aggiuntive.

Questo sembra spezzare il principio di sostituzione di Liskov.

  int[] array = new[] {1, 2, 3}; IList ilist = array; ilist.Add(4); // throws System.NotSupportedException ilist.Insert(0, 0); // throws System.NotSupportedException ilist.Remove(3); // throws System.NotSupportedException ilist.RemoveAt(0); // throws System.NotSupportedException 

Ma non è così. La risposta a questo è che l’esempio ha usato IList / ICollection sbagliato. Se utilizzi un ICollection devi controllare il flag IsReadOnly.

 if (!ilist.IsReadOnly) { ilist.Add(4); ilist.Insert(0, 0); ilist.Remove(3); ilist.RemoveAt(0); } else { // what were you planning to do if you were given a read only list anyway? } 

Se qualcuno ti passa una matrice o una lista, il tuo codice funzionerà bene se controlli la bandiera ogni volta e avrai una riserva … Ma davvero; chi lo fa? Non sai in anticipo se il tuo metodo ha bisogno di un elenco che può richiedere membri aggiuntivi; non lo specifichi nella firma del metodo? Cosa avresti fatto esattamente se ti fosse passato un elenco di sola lettura come int[] ?

È ansible sostituire un List nel codice che utilizza correttamente IList / ICollection . Non è ansible garantire che sia ansible sostituire un IList / ICollection nel codice che utilizza List .

C’è un appello al Principio di Responsabilità Unico / Principio di Segregazione dell’Interfaccia in molti degli argomenti per usare le astrazioni invece dei tipi concreti – dipende dall’interfaccia più stretta ansible. Nella maggior parte dei casi, se si utilizza un List e si pensa di poter utilizzare un’interfaccia più stretta, perché non IEnumerable ? Questo è spesso un adattamento migliore se non è necessario aggiungere elementi. Se è necessario aggiungere alla raccolta, utilizzare il tipo concreto, List .

Per me IList (e ICollection ) è la parte peggiore del framework .NET. IsReadOnly viola il principio di minima sorpresa. Una class, come Array , che non consente mai l’aggiunta, l’inserimento o la rimozione di elementi, non deve implementare un’interfaccia con i metodi Aggiungi, Inserisci e Rimuovi. (vedi anche https://softwareengineering.stackexchange.com/questions/306105/implementing-an-interface-quando-dai-non-ne-una-delle-proprietà )

IList adatto alla tua organizzazione? Se un collega ti chiede di cambiare una firma del metodo per usare IList invece di List , chiedi loro come aggiungerebbero un elemento a un IList . Se non conoscono IsReadOnly (e la maggior parte delle persone non lo fa), allora non usare IList . Mai.


Notare che il flag IsReadOnly proviene da ICollection e indica se gli oggetti possono essere aggiunti o rimossi dalla collezione; ma solo per confondere veramente le cose, non indica se possono essere sostituite, il che nel caso degli Array (che restituiscono IsReadOnlys == true) può essere.

Per ulteriori informazioni su IsReadOnly, vedere la definizione msdn di ICollection .IsReadOnly

List è un’implementazione specifica di IList , che è un contenitore che può essere indirizzato allo stesso modo di un array lineare T[] usando un indice intero. Quando si specifica IList come tipo dell’argomento del metodo, si specifica solo che sono necessarie determinate funzionalità del contenitore.

Ad esempio, la specifica dell’interfaccia non applica una struttura dati specifica da utilizzare. L’implementazione di List avviene con le stesse prestazioni per l’accesso, l’eliminazione e l’aggiunta di elementi come array lineare. Tuttavia, si può immaginare un’implementazione supportata da un elenco collegato, per il quale l’aggiunta di elementi alla fine è più economica (tempo costante) ma l’accesso casuale è molto più costoso. (Si noti che .NET LinkedList non implementa IList .)

Questo esempio indica anche che potrebbero esserci situazioni in cui è necessario specificare l’implementazione, non l’interfaccia, nell’elenco degli argomenti: In questo esempio, ogni volta che si richiede una particolare caratteristica delle prestazioni di accesso. Questo di solito è garantito per un’implementazione specifica di un container (Documentazione List : “Implementa l’interfaccia generica IList usando un array le cui dimensioni sono aumentate dynamicmente come richiesto.”).

Inoltre, potresti voler considerare l’esposizione della minima funzionalità di cui hai bisogno. Per esempio. se non è necessario modificare il contenuto dell’elenco, è consigliabile considerare l’utilizzo di IEnumerable , che IList estende.

Vorrei girare la domanda un po ‘, invece di giustificare il motivo per cui dovresti usare l’interfaccia sull’implementazione concreta, provare a giustificare il motivo per cui dovresti usare l’implementazione concreta piuttosto che l’interfaccia. Se non puoi giustificarlo, usa l’interfaccia.

IList è un’interfaccia in modo da poter ereditare un’altra class e implementare ancora IList mentre l’ereditarietà di List ti impedisce di farlo.

Ad esempio se c’è una class A e la tua class B la eredita, allora non puoi usare List

 class A : B, IList { ... } 

Un principio di TDD e OOP generalmente è la programmazione di un’interfaccia e non un’implementazione.

In questo caso specifico dal momento che stai essenzialmente parlando di un costrutto linguistico, non di uno personalizzato, in genere non ha importanza, ma ad esempio che hai trovato che List non supportava qualcosa di cui avevi bisogno. Se avessi usato IList nel resto dell’app, potresti estendere List con la tua class personalizzata ed essere ancora in grado di passarlo senza refactoring.

Il costo per farlo è minimo, perché non risparmiarti il ​​mal di testa in seguito? È ciò su cui si basa il principio dell’interfaccia.

 public void Foo(IList list) { // Do Something with the list here. } 

In questo caso puoi passare a qualsiasi class che implementa l’interfaccia IList . Se hai usato List , invece, può essere passata solo un’istanza di .

Il modo IList è più liberamente accoppiato rispetto al modo List .

Il caso più importante per l’utilizzo delle interfacce rispetto alle implementazioni è nei parametri della tua API. Se la tua API accetta un parametro List, chiunque lo utilizzi deve utilizzare List. Se il tipo di parametro è IList, il chiamante ha molta più libertà e può utilizzare classi di cui non hai mai sentito parlare, che potrebbero non essere esistite nemmeno quando il tuo codice è stato scritto.

Supprendendo che nessuna di queste domande di Lista vs IList (o risposte) menzioni la differenza di firma. (È per questo che ho cercato questa domanda su SO!)

Quindi ecco i metodi contenuti da List che non si trovano in IList, almeno a partire da .NET 4.5 (circa 2015)

  • AddRange
  • AsReadOnly
  • BinarySearch
  • Capacità
  • ConvertAll
  • esiste
  • Trova
  • Trova tutto
  • FindIndex
  • FindLast
  • FindLastIndex
  • Per ciascuno
  • getRange
  • InsertRange
  • LastIndexOf
  • Rimuovi tutto
  • removeRange
  • Inverso
  • Ordinare
  • ToArray
  • TrimExcess
  • TrueForAll

Cosa succede se .NET 5.0 sostituisce System.Collections.Generic.List in System.Collection.Generics.LinearList . .NET ha sempre il nome List ma garantisce che IList è un contratto. Quindi IMHO noi (almeno io) non dovremmo usare il nome di qualcuno (anche se in questo caso è .NET.) E finire nei guai in seguito.

In caso di utilizzo di IList , il chiamante è sempre all’apparenza guareented e l’implementatore è libero di cambiare la raccolta sottostante a qualsiasi implementazione concreta alternativa di IList

Tutti i concetti sono fondamentalmente affermati nella maggior parte delle risposte sopra per quanto riguarda il motivo per cui utilizzare l’interfaccia rispetto alle implementazioni concrete.

 IList defines those methods (not including extension methods) 

IList Collegamento MSDN

  1. Inserisci
  2. Chiaro
  3. contiene
  4. Copia a
  5. GetEnumerator
  6. Indice di
  7. Inserire
  8. Rimuovere
  9. RemoveAt

List implementa questi nove metodi (esclusi i metodi di estensione), in più ha circa 41 metodi pubblici, che pesa sulla considerazione di quale utilizzare nella propria applicazione.

List Collegamento MSDN

Dovresti definire un IList o un ICollection per altre implementazioni delle tue interfacce.

Si potrebbe desiderare di avere un IOrderRepository che definisce una collezione di ordini in un IList o ICollection. Potresti quindi avere diversi tipi di implementazioni per fornire un elenco di ordini a patto che siano conformi alle “regole” definite dal tuo IList o ICollection.

L’interfaccia garantisce di ottenere almeno i metodi che ti aspetti ; essere a conoscenza della definizione dell’interfaccia, ad es. tutti i metodi astratti che devono essere implementati da qualsiasi class che eredita l’interfaccia. quindi se qualcuno fa una grande class con diversi metodi oltre a quelli che ha ereditato dall’interfaccia per alcune funzionalità di aggiunta, e quelli non sono utili a te, è meglio usare un riferimento a una sottoclass (in questo caso il interfaccia) e assegnare ad esso l’object class concreto.

Un ulteriore vantaggio è che il tuo codice è al riparo da qualsiasi modifica alla class concreta poiché ti stai iscrivendo solo a pochi dei metodi della class concreta e quelli sono quelli che ci saranno finché la class concreta erediterà dall’interfaccia che sei utilizzando. quindi la sua sicurezza per te e la libertà per il programmatore che sta scrivendo un’implementazione concreta per cambiare o aggiungere più funzionalità alla sua class concreta.

IList <> è quasi sempre preferibile come da consiglio dell’altro poster, tuttavia si noti che c’è un bug in .NET 3.5 sp 1 quando si esegue un IList <> attraverso più di un ciclo di serializzazione / deserializzazione con il DataContractSerializer WCF.

Ora c’è un SP per correggere questo bug: KB 971030

Puoi osservare questo argomento da diversi punti di vista, incluso quello di un approccio puramente OO che dice di programmare contro un’interfaccia non un’implementazione. Con questo pensiero, l’utilizzo di IList segue lo stesso principio del passaggio e dell’uso di interfacce definite dall’utente. Credo anche nei fattori di scalabilità e flessibilità forniti da un’interfaccia in generale. Se una class che impone IList deve essere estesa o modificata, il codice che consuma non deve cambiare; sa a cosa si attiene il contratto di IList Interface. Tuttavia utilizzando un’implementazione concreta e List su una class che cambia, potrebbe anche essere necessario modificare il codice chiamante. Questo perché una class che aderisce a IList garantisce un determinato comportamento che non è garantito da un tipo concreto usando List .

Avendo anche il potere di fare qualcosa come modificare l’implementazione predefinita di List su una class Implementing IList per dire che .Add, .Remove o qualsiasi altro metodo IList offre allo sviluppatore molta flessibilità e potenza, altrimenti predefinito dalla lista

In genere, un buon approccio consiste nell’utilizzare IList nell’API pubblica (se necessario e elencare la semantica) e quindi Elenca internamente per implementare l’API. Ciò ti consente di passare a una diversa implementazione di IList senza rompere il codice che utilizza la tua class.

L’elenco dei nomi di class può essere modificato nel prossimo framework .net ma l’interfaccia non cambierà mai in quanto l’interfaccia è un contratto.

Tieni presente che, se la tua API verrà utilizzata solo nei cicli foreach, ecc., Potresti prendere in considerazione la semplice esposizione di IEnumerable.