Elenco <T> è una classe e implementa entrambe le interfacce ICollection <T> e IEnumerable <T> . Inoltre, ICollection <T> estende l'interfaccia IEnumerable <T>. Non sono intercambiabili, almeno non da tutti i punti di vista.
Se si dispone di un Elenco <T>, si garantisce che questo oggetto implementa i metodi e le proprietà richiesti per essere implementati dall'interfaccia ICollection <T> e IEnumerable <T>. Il compilatore lo sa e ti è permesso di lanciarli "verso il basso" in una ICollection <T> o in un IEnumerable <T> implicitamente. Tuttavia, se hai un ICollection <T> devi prima controllare esplicitamente nel tuo codice se è un Elenco <T> o qualcos'altro, forse un Dizionario <T> (dove T è un KeyValuePair ) prima di lanciarlo in quello che desiderio.
Sai che ICollection estende IEnumerable, quindi puoi eseguirne il cast in un IEnumerable. Ma se hai solo un IEnumerable, di nuovo non hai la garanzia che sia un Elenco. Potrebbe essere, ma potrebbe essere qualcos'altro. Dovresti aspettarti un'eccezione di cast non valida se provi ad esempio a lanciare un Elenco <T> in un dizionario <T>.
Quindi non sono "intercambiabili".
Inoltre, ci sono molte interfacce generiche, controlla cosa puoi trovare nello spazio dei nomi System.Collections.Generic .
Modifica: per quanto riguarda il tuo commento, non c'è assolutamente alcuna penalità per le prestazioni se usi List <T> o una delle interfacce che implementa. Dovrai comunque creare un nuovo oggetto, controlla il seguente codice:
List<T> list = new List<T>();
ICollection<T> myColl = list;
IEnumerable<T> myEnum = list;
list , myColl e myEnum puntano tutti allo stesso oggetto. Sia che tu lo dichiari come Elenco o ICollection o IEnumerable, sto ancora richiedendo al programma di creare un Elenco. Avrei potuto scrivere questo:
ICollection<T> myColl = new List<T>();
myColl , in fase di esecuzione è ancora un elenco.
Tuttavia , questo è il punto più importante ... per ridurre l'accoppiamento e aumentare la manutenibilità dovresti sempre dichiarare le tue variabili e i parametri del metodo usando il minimo denominatore possibile per te, che si tratti di un'interfaccia o di una classe astratta o concreta.
Immagina che l'unica cosa di cui il metodo "PerformOperation" ha bisogno è enumerare gli elementi, fare un po 'di lavoro ed uscire, in tal caso non hai bisogno dei cento metodi disponibili in Elenco <T>, hai solo bisogno di ciò che è disponibile in IEnumerable <T >, quindi è necessario applicare quanto segue:
public void PerformOperation(IEnumerable<T> myEnumeration) { ... }
In questo modo, tu e altri sviluppatori sapete che qualsiasi oggetto di una classe che implementa l'interfaccia IEnumerable <T> può essere assegnato a questo metodo. Può essere un elenco, un dizionario o una classe di raccolta personalizzata scritta da un altro sviluppatore.
Se al contrario specifichi che hai esplicitamente bisogno di un Elenco concreto <T> (e sebbene raramente sia il caso nella vita reale, può ancora succedere), tu e altri sviluppatori sapete che deve essere un Elenco o un'altra eredità di una classe concreta dalla lista.