Domanda

Qualcuno può spiegarmi perché dovrei usare IList over List in C #?

Domanda correlata: Perché è considerato male esporre List < T >

È stato utile?

Soluzione

Se stai esponendo la tua classe attraverso una libreria che altri useranno, generalmente vorrai esporla tramite interfacce piuttosto che implementazioni concrete. Questo ti aiuterà se decidi di cambiare l'implementazione della tua classe in un secondo momento per usare una diversa classe concreta. In tal caso, gli utenti della tua libreria non dovranno aggiornare il loro codice poiché l'interfaccia non cambia.

Se lo stai usando solo internamente, potresti non preoccupartene troppo e usare Elenco < T > potrebbe andare bene.

Altri suggerimenti

La risposta meno popolare è che ai programmatori piace far finta che il loro software verrà riutilizzato in tutto il mondo, quando in realtà la maggior parte dei progetti sarà gestita da una piccola quantità di persone e per quanto siano buoni i soundbite correlati all'interfaccia, tu ti stai illudendo.

Astronauti di architettura . Le possibilità che tu abbia mai scritto la tua IList che aggiunga qualcosa a quelle già presenti nel framework .NET sono così remote che i suoi jelly tots teorici sono riservati per "le migliori pratiche".

Software astronauts

Ovviamente se ti viene chiesto quale usi durante un'intervista, dici IList, sorridi ed entrambi ti compiaci per essere così intelligente. O per un'API pubblica, IList. Spero che tu ottenga il mio punto.

L'interfaccia è una promessa (o un contratto).

Come sempre con le promesse - più piccolo è il migliore .

Alcune persone dicono " usa sempre IList < T > invece di Elenco < T > " ;.
Vogliono che cambi le firme del metodo da void Foo (Elenco < T > input) a void Foo (IList < T > input) .

Queste persone hanno torto.

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

Come oggetto secondario secondario, potresti trovare che ogni singolo chiamante ha bisogno di un Elenco < T > e il codice chiamante è disseminato di .ToList ()

Ma molto più importante, se stai accettando un IList come parametro, ti conviene fare attenzione, perché IList < T > e List < T > ; non si comportano allo stesso modo. Nonostante la somiglianza nel nome e nonostante condividano una interfaccia , non espongono lo stesso contratto .

Supponi di avere questo metodo:

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

Un collega utile "refactor" il metodo per accettare IList < int > .

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

Il Principio di sostituzione di Liskov (semplificato) afferma che un tipo derivato dovrebbe essere in grado di essere utilizzato al posto di un tipo di base, senza precondizioni o postcondizioni aggiuntive.

Questo sembra infrangere il principio di sostituzione di Liskov.

 int[] array = new[] {1, 2, 3};
 IList<int> 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 lo fa. La risposta a questa domanda è che l'esempio ha usato IList < T > / ICollection < T > sbagliato. Se utilizzi un ICollection < T > 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 un array o un elenco, il tuo codice funzionerà bene se controlli la bandiera ogni volta e hai un fallback ... 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 passata una lista di sola lettura come int [] ?

Puoi sostituire un Elenco < T > nel codice che utilizza IList < T > / ICollection < T > correttamente . non puoi garantire di poter sostituire un IList < T > / ICollection < T > nel codice che utilizza List < T > .

C'è un appello al principio di responsabilità singola / principio di segregazione dell'interfaccia in molti degli argomenti per usare le astrazioni anziché i tipi concreti - dipende dall'interfaccia più stretta possibile. Nella maggior parte dei casi, se stai utilizzando un Elenco < T > e pensi di poter invece utilizzare un'interfaccia più stretta - perché non IEnumerable < T > ? Questo è spesso una soluzione migliore se non è necessario aggiungere elementi. Se devi aggiungere alla raccolta, usa il tipo concreto, Elenco < T > .

Per me IList < T > (e ICollection < T > ) è la parte peggiore del framework .NET. IsReadOnly viola il principio della minima sorpresa. Una classe, come Array , che non consente mai di aggiungere, inserire o rimuovere elementi non dovrebbe implementare un'interfaccia con i metodi Aggiungi, Inserisci e Rimuovi. (vedi anche https: // softwareengineering .stackexchange.com / domande / 306.105 / implementazione-an-interfaccia-quando-si-dont-bisogno-di-the-proprietà )

IList < T > è adatto alla tua organizzazione? Se un collega ti chiede di cambiare la firma di un metodo per usare IList < T > invece di Elenco < T > , chiedi loro come aggiungerebbero un elemento a un IList < T > . Se non conoscono IsReadOnly (e la maggior parte delle persone non lo fanno), non utilizzare IList < T > . Mai.


Nota che il flag IsReadOnly proviene da ICollection < T > ;, e indica se gli elementi possono essere aggiunti o rimossi dalla raccolta; ma solo per confondere davvero le cose, non indica se possono essere sostituite, cosa che nel caso degli array (che restituiscono IsReadOnlys == true) può essere.

Per ulteriori informazioni su IsReadOnly, vedere definizione msdn di ICollection < T > .IsReadOnly

Elenco < T > è un'implementazione specifica di IList < T > , che è un contenitore che può essere indirizzato allo stesso modo di un array lineare T [] utilizzando un indice intero. Quando specifichi IList < T > come tipo di argomento del metodo, specifichi solo che hai bisogno di determinate capacità del contenitore.

Ad esempio, la specifica dell'interfaccia non impone una struttura dati specifica da utilizzare. L'implementazione di List < T > avviene con le stesse prestazioni per l'accesso, l'eliminazione e l'aggiunta di elementi come un array lineare. Tuttavia, è possibile immaginare un'implementazione supportata da un elenco collegato, per cui l'aggiunta di elementi alla fine è più economica (tempo costante) ma l'accesso casuale molto più costoso. (Nota che .NET LinkedList < T > non non implementa IList < T > .)

Questo esempio indica anche che potrebbero essere presenti situazioni in cui è necessario specificare l'implementazione, non l'interfaccia, nell'elenco degli argomenti: In questo esempio, ogni volta che si richiede una caratteristica di prestazione di accesso particolare. Questo è generalmente garantito per un'implementazione specifica di un contenitore ( Elenco < T > documentazione: " Implementa l'interfaccia generica IList < T > usando un array le cui dimensioni sono dinamicamente aumentato secondo necessità. ").

Inoltre, potresti prendere in considerazione l'idea di esporre la minima funzionalità di cui hai bisogno. Per esempio. se non hai bisogno di cambiare il contenuto dell'elenco, dovresti probabilmente considerare di usare IEnumerable < T > , che IList < T > estende.

Vorrei invertire un po 'la domanda, invece di giustificare perché dovresti usare l'interfaccia sull'implementazione concreta, proverei a giustificare perché dovresti usare l'implementazione concreta piuttosto che l'interfaccia. Se non puoi giustificarlo, usa l'interfaccia.

IList < T > è un'interfaccia in modo da poter ereditare un'altra classe e comunque implementare IList < T > pur ereditando Elenco < T > ti impedisce di farlo.

Ad esempio, se esiste una classe A e la tua classe B la eredita, non puoi utilizzare Elenco < T >

class A : B, IList<T> { ... }

Un principio di TDD e OOP è in genere la programmazione di un'interfaccia e non di un'implementazione.

In questo caso specifico, dato che stai essenzialmente parlando di un costrutto linguistico, non di uno personalizzato, in genere non importerà, ma supponiamo, ad esempio, che List non abbia supportato qualcosa di cui avevi bisogno. Se avessi usato IList nel resto dell'app, avresti potuto estendere List con la tua classe personalizzata ed essere comunque in grado di farlo passare senza refactoring.

Il costo per farlo è minimo, perché non risparmiarti il ??mal di testa in seguito? È il principio dell'interfaccia.

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

In questo caso potresti passare in qualsiasi classe che implementa IList < Bar > interfaccia. Se hai utilizzato Elenco < Bar > invece, solo un elenco < Bar > l'istanza potrebbe essere passata.

L'IList < Bar > il modo in cui è accoppiato più liberamente rispetto alla Lista < Bar > modo.

Il caso più importante per l'utilizzo delle interfacce rispetto alle implementazioni è nei parametri dell'API. Se la tua API accetta un parametro Elenco, chiunque lo usi deve usare Elenco. Se il tipo di parametro è IList, il chiamante ha molta più libertà e può usare classi di cui non hai mai sentito parlare, che potrebbero non esistere nemmeno al momento della scrittura del codice.

Supponendo che nessuna di queste domande della Lista contro IList (o risposte) menzioni la differenza di firma. (Ecco perché ho cercato questa domanda su SO!)

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

  • AddRange
  • AsReadOnly
  • BinarySearch
  • Capacità
  • ConvertAll
  • esiste
  • Trova
  • FindAll
  • FindIndex
  • FindLast
  • FindLastIndex
  • ForEach
  • getRange
  • InsertRange
  • LastIndexOf
  • RemoveAll
  • removeRange
  • Reverse
  • Ordina
  • ToArray
  • TrimExcess
  • TrueForAll

Cosa succede se .NET 5.0 sostituisce System.Collections.Generic.List < T > in System.Collection.Generics.LinearList < T > . .NET possiede sempre il nome Elenco < T > ma garantiscono che IList < T > è un contratto. Quindi IMHO noi (almeno I) non dovremmo usare il nome di qualcuno (anche se in questo caso è .NET) e metterci nei guai in seguito.

Nel caso in cui si utilizzi IList < T > , il chiamante è sempre in forma di cose guaraentate per funzionare, e l'implementatore è libero di cambiare la raccolta sottostante in qualsiasi implementazione alternativa alternativa di IList

Tutti i concetti sono fondamentalmente indicati nella maggior parte delle risposte sopra riguardanti il ??motivo per cui utilizzare l'interfaccia su implementazioni concrete.

IList<T> defines those methods (not including extension methods)

IList < T > collegamento MSDN

  1. Aggiungi
  2. Cancella
  3. contiene
  4. CopyTo
  5. GetEnumerator
  6. IndexOf
  7. Inserisci
  8. Rimuovi
  9. RemoveAt

Elenco < T > implementa questi nove metodi (esclusi i metodi di estensione), in più ha circa 41 metodi pubblici, che pesa in considerazione di quale utilizzare nella tua applicazione.

Elenco < T > collegamento MSDN

Lo faresti perché la definizione di un IList o di un ICollection si aprirebbe per altre implementazioni delle tue interfacce.

Potresti voler avere un IOrderRepository che definisce una raccolta di ordini in un IList o ICollection. Potresti quindi avere diversi tipi di implementazioni per fornire un elenco di ordini purché conformi alle "regole" definito da IList o ICollection.

L'interfaccia ti assicura almeno di ottenere i metodi che ti aspetti ; essere consapevoli della definizione dell'interfaccia cioè. tutti i metodi astratti che sono lì per essere implementati da qualsiasi classe che eredita l'interfaccia. quindi se qualcuno crea una sua enorme classe con diversi metodi oltre a quelli che ha ereditato dall'interfaccia per alcune funzionalità aggiuntive e quelli che non ti sono utili, è meglio usare un riferimento a una sottoclasse (in questo caso il interfaccia) e assegnargli l'oggetto classe concreto.

vantaggio aggiuntivo è che il tuo codice è al sicuro da qualsiasi modifica alla classe concreta poiché ti stai iscrivendo solo a pochi metodi della classe concreta e quelli sono quelli che rimarranno lì fintanto che la classe concreta eredita dal interfaccia che stai 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 classe concreta.

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

Esiste ora un SP per correggere questo errore: KB 971030

Puoi guardare questo argomento da diverse angolazioni, 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'utilizzo delle interfacce definite dall'utente. Credo anche nei fattori di scalabilità e flessibilità forniti da un'interfaccia in generale. Se una classe che impianta IList < T > deve essere esteso o modificato, il codice di consumo non deve cambiare; sa a cosa aderisce il contratto di IList Interface. Tuttavia, utilizzando un'implementazione concreta e Elenca < T > su una classe che cambia, potrebbe essere necessario modificare anche il codice chiamante. Questo perché una classe che aderisce a IList < T > garantisce un determinato comportamento che non è garantito da un tipo concreto utilizzando List < T > ;.

Avere anche il potere di fare qualcosa come modificare l'implementazione predefinita di List < T > su una classe che implementa IList < T > per esempio .Add, .Remove o qualsiasi altro metodo IList offre allo sviluppatore un lotto di flessibilità e potenza, altrimenti predefinito da List < T >

In genere, un buon approccio consiste nell'utilizzare IList nell'API di fronte al pubblico (quando appropriato e sono necessari elenchi semantici), quindi Elenca internamente per implementare l'API. & nbsp; Ciò ti consente di passare a un'implementazione diversa di IList senza interrompere il codice che utilizza la tua classe.

L'elenco dei nomi di classe può essere modificato nel prossimo framework .net ma l'interfaccia non cambierà mai poiché l'interfaccia è un contratto.

Nota che, se l'API verrà utilizzata solo in cicli foreach, ecc., potresti prendere in considerazione l'idea di esporre IEnumerable invece.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top