Devo sempre restituire IEnumerable <T> invece di IList <T>?


97

Quando scrivo il mio DAL o un altro codice che restituisce un set di articoli, devo sempre fare la mia dichiarazione di reso:

public IEnumerable<FooBar> GetRecentItems()

o

public IList<FooBar> GetRecentItems()

Attualmente, nel mio codice ho provato a utilizzare IEnumerable il più possibile ma non sono sicuro che questa sia la migliore pratica? Sembrava giusto perché stavo restituendo il tipo di dati più generico pur essendo ancora descrittivo di ciò che fa, ma forse questo non è corretto da fare.


1
È List <T> o IList <T> di cui stai chiedendo? Il titolo e la domanda dicono cose diverse ...
Fredrik Mörk

Quando possibile l'interfaccia utente IEnumerable o Ilist instabile di tipo concerete.
Usman Masood

2
Restituisco le raccolte come List <T>. Non vedo la necessità di restituire IEnumberable <T> poiché puoi estrarlo da List <T>
Chuck Conway


possibile duplicato di ienumerablet-as-return-type
nawfal

Risposte:


44

Dipende davvero dal motivo per cui stai utilizzando quella specifica interfaccia.

Ad esempio, IList<T>ha diversi metodi che non sono presenti in IEnumerable<T>:

  • IndexOf(T item)
  • Insert(int index, T item)
  • RemoveAt(int index)

e proprietà:

  • T this[int index] { get; set; }

Se hai bisogno di questi metodi in qualche modo, allora torna con tutti i mezzi IList<T>.

Inoltre, se il metodo che consuma il tuo IEnumerable<T>risultato si aspetta un IList<T>, salverà il CLR dal considerare eventuali conversioni richieste, ottimizzando così il codice compilato.


2
@ Jon FDG consiglia di utilizzare Collection <T> o ReadOnlyCollection <T> come valore restituito per i tipi di raccolta, vedere la mia risposta.
Sam Saffron

Non sei chiaro cosa intendi nella tua ultima frase quando dici "salverà il CLR". Cosa lo salverà, usando IEnumerable vs. IList? Puoi renderlo più chiaro?
PositiveGuy

1
@CoffeeAddict Tre anni dopo questa risposta penso che tu abbia ragione - l'ultima parte è vaga. Se un metodo che prevede un IList <T> come parametro ottiene un IEnumerable <T>, IEnumerable deve essere racchiuso manualmente in un nuovo List <T> o in un altro implementatore IList <T> e quel lavoro non verrà eseguito da il CLR per te. L'opposto: un metodo che prevede un IEnumerable <T> che ottiene un IList <T>, potrebbe dover eseguire un po 'di unboxing, ma a ben vedere potrebbe non essere necessario perché IList <T> implementa IEnumerable <T>.
Jon Limjap

68

Le linee guida per la progettazione del framework consigliano di usare la classe Collection quando è necessario restituire una raccolta modificabile dal chiamante o ReadOnlyCollection per le raccolte di sola lettura.

Il motivo per cui questo è preferito a un semplice IListè che IListnon informa il chiamante se è di sola lettura o meno.

Se IEnumerable<T>invece restituisci un , alcune operazioni potrebbero essere un po 'più complicate da eseguire per il chiamante. Inoltre non darai più al chiamante la flessibilità di modificare la raccolta, qualcosa che potresti o non potresti volere.

Tieni presente che LINQ contiene alcuni assi nella manica e ottimizzerà determinate chiamate in base al tipo su cui vengono eseguite. Quindi, ad esempio, se esegui una Counte la raccolta sottostante è una Lista, NON passerà attraverso tutti gli elementi.

Personalmente, per un ORM probabilmente mi atterrei Collection<T>come valore di ritorno.


12
Le Linee guida per le raccolte contengono un elenco più dettagliato di DO e DONT.
Chaquotay

26

In generale, dovresti richiedere il più generico e restituire la cosa più specifica possibile. Quindi, se hai un metodo che accetta un parametro e hai solo bisogno di ciò che è disponibile in IEnumerable, allora quello dovrebbe essere il tuo tipo di parametro. Se il tuo metodo può restituire un IList o un IEnumerable, preferisci restituire IList. Ciò garantisce che sia utilizzabile dalla più ampia gamma di consumatori.

Sii libero in ciò che richiedi ed esplicito in ciò che fornisci.


1
Sono giunto alla conclusione opposta: che si dovrebbero accettare tipi specifici e restituire tipi generali. Tuttavia, potresti avere ragione sul fatto che metodi più ampiamente applicabili sono migliori di metodi più vincolati. Dovrò pensarci di più.
Dave Cousineau

3
La giustificazione per accettare tipi generali come input è che consente di lavorare con la più ampia gamma di input possibile al fine di ottenere il massimo riutilizzo possibile da un componente. D'altra parte, poiché sai già esattamente che tipo di oggetto hai a portata di mano, non ha molto senso mascherarlo.
Mel

1
Penso di essere d'accordo con avere parametri più generali, ma qual è il tuo motivo per restituire qualcosa di meno generale?
Dave Cousineau

6
Ok, fammi provare in un altro modo. Perché dovresti buttare via le informazioni? Se ti interessa solo che il risultato sia IEnumerable <T>, ti fa male in qualche modo sapere che è un IList <T>? No, non è così. Può essere un'informazione superflua in alcuni casi, ma non ti danneggia. Ora per il vantaggio. Se restituisci un List o IList, posso dire immediatamente che la raccolta è già stata recuperata, cosa che non posso sapere con un IEnumerable. Questa può o non può essere un'informazione utile, ma ancora una volta, perché dovresti buttare via le informazioni? Se conosci informazioni extra su qualcosa, passale.
Mel

In retrospettiva, sono sorpreso che nessuno mi abbia mai chiamato per quello. Un IEnumerable SAREBBE stato già recuperato. Tuttavia, un IQueryable potrebbe essere restituito in uno stato non enumerato. Quindi forse un esempio migliore restituirebbe IQueryable contro IEnumerable. La restituzione di IQueryable più specifica consentirebbe un'ulteriore composizione, mentre la restituzione di IEnumerable forzerebbe l'enumerazione immediata e diminuirebbe la componibilità del metodo.
Mel

23

Dipende...

Restituire il tipo meno derivato ( IEnumerable) ti lascerà il maggior margine di manovra per cambiare l'implementazione sottostante lungo il percorso.

La restituzione di un tipo più derivato ( IList) fornisce agli utenti della tua API più operazioni sul risultato.

Suggerirei sempre di restituire il tipo meno derivato che ha tutte le operazioni di cui i tuoi utenti avranno bisogno ... quindi in pratica, devi prima deremine quali operazioni sul risultato hanno senso nel contesto dell'API che stai definendo.


bella risposta generica in quanto si applica anche ad altri metodi.
user420667

1
So che questa risposta è molto vecchia ... ma sembra contraddire la documentazione: "Se né l'interfaccia IDictionary <TKey, TValue> né l'interfaccia IList <T> soddisfano i requisiti della raccolta richiesta, derivare la nuova classe della raccolta da l'interfaccia ICollection <T> invece per una maggiore flessibilità "Questo sembra implicare che dovrebbe essere preferito il tipo più derivato. ( msdn.microsoft.com/en-us/library/92t2ye13(v=vs.110).aspx ) \
DeborahK

11

Una cosa da considerare è che se stai usando un'istruzione LINQ a esecuzione differita per generare la tua IEnumerable<T>, chiamare .ToList()prima di tornare dal tuo metodo significa che i tuoi elementi possono essere iterati due volte: una volta per creare l'elenco e una volta quando il chiamante esegue il ciclo , filtra o trasforma il valore restituito. Quando pratico, mi piace evitare di convertire i risultati di LINQ-to-Objects in un elenco o dizionario concreto finché non è necessario. Se il mio chiamante ha bisogno di un elenco, è una semplice chiamata con un metodo semplice: non ho bisogno di prendere quella decisione per loro e questo rende il mio codice leggermente più efficiente nei casi in cui il chiamante sta solo facendo un foreach.


@ Joel Mueller, di solito li chiamerei comunque ToList (). In genere non mi piace esporre IQueryable al resto dei miei progetti.
KingNestor

4
Mi riferivo più a LINQ-to-Objects, dove IQueryable generalmente non entra nell'immagine. Quando è coinvolto un database, ToList () diventa più necessario, perché altrimenti rischi di chiudere la connessione prima di iterare, il che non funziona molto bene. Tuttavia, quando questo non è un problema, è abbastanza facile esporre IQueryable come IEnumerable senza forzare un'iterazione aggiuntiva quando si desidera nascondere IQueryable.
Joel Mueller

9

List<T>offre al codice chiamante molte altre funzionalità, come la modifica dell'oggetto restituito e l'accesso tramite indice. Quindi la domanda si riduce a: nel caso d'uso specifico della tua applicazione, VUOI supportare tali usi (presumibilmente restituendo una raccolta appena costruita!), Per comodità del chiamante - o vuoi velocità per il caso semplice quando tutto il il chiamante ha bisogno di scorrere la raccolta e si può tranquillamente restituire un riferimento a una vera raccolta sottostante senza temere che ciò possa essere modificato erroneamente, ecc.?

Solo tu puoi rispondere a questa domanda e solo comprendendo bene cosa vorranno fare i tuoi chiamanti con il valore di ritorno e quanto sia importante la performance qui (quanto sono grandi le raccolte che copieresti, quanto è probabile che si tratti di un collo di bottiglia, eccetera).


"restituisci in modo sicuro un riferimento a una vera raccolta sottostante senza temere che questo venga modificato erroneamente" - Anche se restituisci IEnumerable <T>, non potrebbero semplicemente restituirlo a List <T> e cambiarlo?
Kobi

Non tutti gli IEnumarable <T> sono anche List <T>. Se l'oggetto restituito non è di un tipo che eredita da List <T> o implementa IList <T>, si otterrebbe un'eccezione InvalidCastException.
lowglider

2
List <T> ha il problema che ti blocca in una particolare implementazione Collection <T> o ReadOnlyCollection <T> sono preferiti
Sam Saffron

4

Penso che tu possa usare entrambi, ma ognuno ha un uso. Fondamentalmente Listè IEnumerablema hai la funzionalità di conteggio, aggiungi elemento, rimuovi elemento

IEnumerable non è efficiente per il conteggio degli elementi

Se la raccolta è destinata alla sola lettura o la modifica della raccolta è controllata da, Parentrestituire un IListsolo per Countnon è una buona idea.

In Linq, esiste un Count()metodo di estensione su IEnumerable<T>cui all'interno del CLR si collegherà .Countse il tipo sottostante è di IList, quindi la differenza di prestazioni è trascurabile.

In genere ritengo (opinione) che sia meglio restituire IEnumerable ove possibile, se è necessario fare aggiunte, aggiungere questi metodi alla classe genitore, altrimenti il ​​consumatore gestisce la raccolta all'interno del Modello che viola i principi, ad es. manufacturer.Models.Add(model)Viola la legge di demetra. Naturalmente queste sono solo linee guida e non regole rigide e veloci, ma finché non si ha una piena comprensione dell'applicabilità, seguire ciecamente è meglio che non seguire affatto.

public interface IManufacturer 
{
     IEnumerable<Model> Models {get;}
     void AddModel(Model model);
}

(Nota: se si utilizza nNHibernate, potrebbe essere necessario eseguire il mapping a IList privato utilizzando diverse funzioni di accesso.)


2

Non è così semplice quando parli di valori di ritorno invece di parametri di input. Quando si tratta di un parametro di input, sai esattamente cosa devi fare. Quindi, se devi essere in grado di iterare sulla raccolta, prendi un IEnumberable mentre se devi aggiungere o rimuovere, prendi un IList.

Nel caso di un valore restituito, è più difficile. Cosa si aspetta il tuo chiamante? Se restituisci un IEnumerable, allora non saprà a priori che può ricavarne un IList. Ma, se restituisci un IList, saprà che può iterare su di esso. Quindi, devi prendere in considerazione ciò che il tuo chiamante farà con i dati. La funzionalità di cui il chiamante ha bisogno / si aspetta è ciò che dovrebbe governare quando si prende la decisione su cosa restituire.


0

come tutti hanno detto, dipende, se non si desidera aggiungere / rimuovere funzionalità al livello di chiamata, voterò per IEnumerable in quanto fornisce solo l'iterazione e le funzionalità di base che in prospettiva di progettazione mi piacciono. Restituendo IList i miei voti sono sempre againist ma è principalmente quello che ti piace e cosa no. in termini di prestazioni penso che siano più simili.


0

Se non conti nel tuo codice esterno è sempre meglio restituire IEnumerable, perché in seguito puoi modificare la tua implementazione (senza impatto sul codice esterno), ad esempio, per produrre logica iteratrice e conservare le risorse di memoria (tra l'altro un'ottima funzionalità del linguaggio ).

Tuttavia, se hai bisogno di contare gli elementi, non dimenticare che c'è un altro livello tra IEnumerable e IList - ICollection .


0

Potrei essere un po 'fuori di testa qui, visto che nessun altro me lo ha suggerito finora, ma perché non ritorni (I)Collection<T>?

Da quello che ricordo, Collection<T>era il tipo restituito preferito List<T>perché astrae l'implementazione. Tutti implementano IEnumerable, ma mi sembra un livello un po 'troppo basso per il lavoro.


0

Penso che tu possa usare entrambi, ma ognuno ha un uso. Fondamentalmente Listè IEnumerablema hai la funzionalità di conteggio, Aggiungi elemento, Rimuovi elemento

IEnumerable non è efficiente per il conteggio di elementi o per ottenere un elemento specifico nella raccolta.

List è una collezione ideale per trovare elementi specifici, aggiungere elementi o rimuoverli facilmente.

Generalmente cerco di utilizzare Listdove possibile in quanto questo mi dà maggiore flessibilità.

Usa List<FooBar> getRecentItems() invece di IList<FooBar> GetRecentItems()


0

Penso che la regola generale sia quella di utilizzare la classe più specifica per tornare, per evitare di fare lavori non necessari e dare al chiamante più opzioni.

Detto questo, penso che sia più importante considerare il codice di fronte a te che stai scrivendo rispetto al codice che scriverà il prossimo ragazzo (entro limiti ragionevoli). Questo perché puoi fare supposizioni sul codice che già esiste.

Ricorda che lo spostamento SU a una raccolta da IEnumerable in un'interfaccia funzionerà, mentre lo spostamento verso il basso a IEnumerable da una raccolta interromperà il codice esistente.

Se queste opinioni sembrano tutte contrastanti, è perché la decisione è soggettiva.


0

TL; DR; - sommario

  • Se sviluppi software in-house, usa il tipo specifico (Like List) per i valori di ritorno e il tipo più generico per i parametri di input anche in caso di collezioni.
  • Se un metodo fa parte dell'API pubblica di una libreria ridistribuibile, utilizzare le interfacce anziché i tipi di raccolta concreti per introdurre sia i valori di ritorno che i parametri di input.
  • Se un metodo restituisce una raccolta di sola lettura, mostrarla utilizzando IReadOnlyListo IReadOnlyCollectioncome tipo di valore restituito.

Di Più

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.