Elenco IEnumerable vs - Cosa usare? Come funzionano?


678

Ho dei dubbi su come funzionano gli enumeratori e su LINQ. Considera queste due semplici selezioni:

List<Animal> sel = (from animal in Animals 
                    join race in Species
                    on animal.SpeciesKey equals race.SpeciesKey
                    select animal).Distinct().ToList();

o

IEnumerable<Animal> sel = (from animal in Animals 
                           join race in Species
                           on animal.SpeciesKey equals race.SpeciesKey
                           select animal).Distinct();

Ho cambiato i nomi dei miei oggetti originali in modo che questo assomigli ad un esempio più generico. La query in sé non è così importante. Quello che voglio chiedere è questo:

foreach (Animal animal in sel) { /*do stuff*/ }
  1. Ho notato che se uso IEnumerable, quando eseguo il debug e ispeziono "sel", che in quel caso è IEnumerable, ha alcuni membri interessanti: "inner", "outer", "innerKeySelector" e "outerKeySelector", questi ultimi 2 appaiono essere delegati. Il membro "interno" non contiene istanze "Animali", ma piuttosto istanze "Specie", il che è stato molto strano per me. Il membro "esterno" contiene istanze "Animali". Presumo che i due delegati determinino quale entra e cosa ne esce?

  2. Ho notato che se uso "Distinto", "interno" contiene 6 elementi (questo non è corretto poiché solo 2 sono Distinti), ma "esterno" contiene i valori corretti. Ancora una volta, probabilmente i metodi delegati determinano questo, ma questo è un po 'più di quanto io sappia di IEnumerable.

  3. Ancora più importante, quale delle due opzioni è la migliore in termini di prestazioni?

La conversione della Lista del male tramite .ToList()?

O forse usando direttamente l'enumeratore?

Se puoi, spiega anche un po 'o lancia alcuni link che spiegano questo uso di IEnumerable.

Risposte:


741

IEnumerabledescrive il comportamento, mentre List è un'implementazione di quel comportamento. Quando lo usi IEnumerable, dai al compilatore la possibilità di rimandare il lavoro a dopo, eventualmente ottimizzando lungo il percorso. Se si utilizza ToList () si impone al compilatore di modificare immediatamente i risultati.

Ogni volta che sto "impilando" espressioni LINQ, lo uso IEnumerable, perché specificando solo il comportamento do a LINQ la possibilità di rinviare la valutazione e possibilmente ottimizzare il programma. Ricordate come LINQ non genera l'SQL per eseguire una query sul database fino a quando non lo enumerate? Considera questo:

public IEnumerable<Animals> AllSpotted()
{
    return from a in Zoo.Animals
           where a.coat.HasSpots == true
           select a;
}

public IEnumerable<Animals> Feline(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Felidae"
           select a;
}

public IEnumerable<Animals> Canine(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Canidae"
           select a;
}

Ora hai un metodo che seleziona un campione iniziale ("AllSpotted"), oltre ad alcuni filtri. Quindi ora puoi farlo:

var Leopards = Feline(AllSpotted());
var Hyenas = Canine(AllSpotted());

Quindi è più veloce usare List over IEnumerable? Solo se si desidera impedire l'esecuzione di una query più di una volta. Ma è nel complesso migliore? Bene in quanto sopra, Leopards e Hyenas vengono convertiti in singole query SQL ciascuna e il database restituisce solo le righe rilevanti. Ma se avessimo restituito un elenco da AllSpotted(), allora potrebbe funzionare più lentamente perché il database potrebbe restituire molti più dati di quanti siano effettivamente necessari e perdiamo cicli facendo il filtro nel client.

In un programma, potrebbe essere meglio rinviare la conversione della query in un elenco fino alla fine, quindi se ho intenzione di enumerare Leopards e Hyenas più di una volta, farei questo:

List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();

11
Penso che si riferiscano ai due lati di un join. Se fai "SELEZIONA * DA Animali UNISCITI a Specie ...", la parte interna dell'unione è Animali e la parte esterna è Specie.
Chris Wenham,

10
Quando ho letto le risposte su: IEnumerable <T> vs IQueryable <T> ho visto la spiegazione analogica, in modo che IEnumerable forza automaticamente il runtime a utilizzare LINQ to Objects per interrogare la raccolta. Quindi sono confuso tra questi 3 tipi. stackoverflow.com/questions/2876616/...
Bronek

4
@Bronek La risposta che hai collegato è vera. IEnumerable<T>sarà LINQ-To-Objects dopo la prima parte, il che significa che tutti gli avvistati dovrebbero essere restituiti per eseguire Feline. D'altra parte IQuertable<T>, consentirà di perfezionare la query, abbattendo solo i felini maculati.
Nate,

21
Questa risposta è molto fuorviante! Il commento di @ Nate spiega perché. Se stai usando IEnumerable <T>, il filtro si verificherà sul lato client, non importa quale.
Hans,

5
Sì, AllSpotted () verrebbe eseguito due volte. Il problema più grande con questa risposta è la seguente dichiarazione: "Bene in quanto sopra, Leopards e Hyenas vengono convertiti in singole query SQL ciascuna e il database restituisce solo le righe pertinenti." Questo è falso, perché la clausola where viene chiamata su un IEnumerable <> e questo sa solo come fare il ciclo tra gli oggetti che provengono già dal database. Se hai reso il ritorno di AllSpotted () e i parametri di Feline () e Canine () in IQueryable, allora il filtro si verificherebbe in SQL e questa risposta avrebbe senso.
Hans,

178

C'è un ottimo articolo scritto da: TechBlog di Claudio Bernasconi qui: Quando usare IEnumerable, ICollection, IList and List

Ecco alcuni punti di base su scenari e funzioni:

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine


25
Va sottolineato che questo articolo è destinato esclusivamente al pubblico che affronta parti del codice, non ai meccanismi interni. Listè un'implementazione di IListe come tale ha funzionalità extra oltre a quelli IList(ad esempio Sort, Find, InsertRange). Se si forza a voi stessi di utilizzare IListoltre List, perdi questi metodi che si possono richiedere
Jonathan Twite

4
Non dimenticareIReadOnlyCollection<T>
Dandré, il

2
Potrebbe essere utile includere anche un array semplice []qui.
jbyrd,

Sebbene possa essere disapprovato, grazie per aver condiviso questa grafica e questo articolo
Daniel,

134

Una classe che implementa IEnumerableti consente di usare la foreachsintassi.

Fondamentalmente ha un metodo per ottenere l'elemento successivo nella raccolta. Non ha bisogno che l'intera collezione sia in memoria e non sa quanti elementi ci siano, foreachcontinua a ricevere l'elemento successivo fino a quando non si esaurisce.

Ciò può essere molto utile in determinate circostanze, ad esempio in una tabella di database di grandi dimensioni che non si desidera copiare l'intera cosa in memoria prima di iniziare l'elaborazione delle righe.

Ora Listimplementa IEnumerable, ma rappresenta l'intera raccolta in memoria. Se ne hai uno IEnumerablee chiami .ToList(), crei un nuovo elenco con i contenuti dell'enumerazione in memoria.

L'espressione linq restituisce un'enumerazione e, per impostazione predefinita, l'espressione viene eseguita quando si scorre attraverso l'utilizzo di foreach. Un'istruzione IEnumerablelinq viene eseguita quando si esegue l'iterazione di foreach, ma è possibile forzarla per iterare prima utilizzando .ToList().

Ecco cosa intendo:

var things = 
    from item in BigDatabaseCall()
    where ....
    select item;

// this will iterate through the entire linq statement:
int count = things.Count();

// this will stop after iterating the first one, but will execute the linq again
bool hasAnyRecs = things.Any();

// this will execute the linq statement *again*
foreach( var thing in things ) ...

// this will copy the results to a list in memory
var list = things.ToList()

// this won't iterate through again, the list knows how many items are in it
int count2 = list.Count();

// this won't execute the linq statement - we have it copied to the list
foreach( var thing in list ) ...

2
Ma cosa succede se si esegue un foreach su un IEnumerable senza prima convertirlo in un elenco ? Porta in memoria l'intera collezione? Oppure, crea un'istanza dell'elemento uno alla volta, mentre scorre sul ciclo foreach? grazie
Pap

@Pap quest'ultimo: viene eseguito di nuovo, nulla viene automaticamente memorizzato nella cache.
Keith,

sembra che il tasto diff sia 1) tutto in memoria o no. 2) IEnumerable fammi usare foreachmentre List andrà per dire indice. Ora, se mi piacerebbe sapere il conteggio / lunghezza di thinganticipo, IEnumerable non sarà di aiuto, giusto?
Jeb50,

@ Jeb50 Non esattamente - entrambi Liste Arrayimplementare IEnumerable. Puoi pensare IEnumerablea un minimo comune denominatore che funziona sia nelle raccolte di memoria sia in quelle di grandi dimensioni che ottengono un elemento alla volta. Quando chiami IEnumerable.Count()potresti chiamare una .Lengthproprietà veloce o passare attraverso l'intera raccolta - il punto è che con IEnumerablete non lo sai. Questo può essere un problema, ma se ci vai solo foreachallora non ti interessa - il tuo codice funzionerà con uno Arrayo DataReaderlo stesso.
Keith,

1
@MFouadKajj Non so quale stack stai usando, ma quasi certamente non sta facendo una richiesta per ogni riga. Il server esegue la query e calcola il punto di partenza del set di risultati, ma non ottiene il tutto. Per insiemi di risultati di piccole dimensioni è probabile che si tratti di un singolo viaggio, per quelli di grandi dimensioni si sta inviando una richiesta per più righe dai risultati, ma non riesegue l'intera query.
Keith,

97

Nessuno ha menzionato una differenza cruciale, ironicamente ha risposto a una domanda chiusa come duplicata di questa.

IEnumerable è di sola lettura e List no.

Vedi Differenza pratica tra Elenco e IEnumerable


Come follow-up, è a causa dell'aspetto Interfaccia o dell'aspetto Elenco? cioè IList è anche di sola lettura?
Jason Masters il

IList non è di sola lettura - docs.microsoft.com/en-us/dotnet/api/… IEnumerable è di sola lettura perché non ha alcun metodo per aggiungere o rimuovere qualsiasi cosa una volta che è stata costruita, è una delle interfacce di base che IList si estende (vedi link)
Bloke CAD,

67

La cosa più importante da capire è che, usando Linq, la query non viene valutata immediatamente. Viene eseguito solo come parte dell'iterazione attraverso il risultante IEnumerable<T>in foreach- è quello che stanno facendo tutti gli strani delegati.

Pertanto, il primo esempio valuta immediatamente la query chiamando ToListe inserendo i risultati della query in un elenco.
Il secondo esempio restituisce uno IEnumerable<T>che contiene tutte le informazioni necessarie per eseguire la query in un secondo momento.

In termini di prestazioni, la risposta è che dipende . Se hai bisogno di valutare i risultati in una sola volta (ad esempio, stai mutando le strutture che stai interrogando in seguito, o se non vuoi che l'iterazione su IEnumerable<T>impieghi a lungo) usa un elenco. Altrimenti usa un IEnumerable<T>. L'impostazione predefinita dovrebbe essere l'uso della valutazione su richiesta nel secondo esempio, poiché generalmente utilizza meno memoria, a meno che non vi sia un motivo specifico per memorizzare i risultati in un elenco.


Ciao e grazie per aver risposto :: -). Questo ha chiarito quasi tutti i miei dubbi. Qualche idea sul perché l'Enumerabile sia "diviso" in "interno" ed "esterno"? Questo succede quando ispeziono l'elemento in modalità debug / break tramite mouse. Questo è forse il contributo di Visual Studio? Enumerare sul posto e indicare input e output dell'Enum?
Axonn

5
Questo è Joinfare il suo lavoro: interno ed esterno sono i due lati dell'unione. In generale, non preoccuparti di ciò che è effettivamente inIEnumerables , poiché sarà completamente diverso dal tuo codice reale. Ti preoccupi solo dell'output effettivo quando
esegui l'

40

Il vantaggio di IEnumerable è l'esecuzione differita (di solito con i database). La query non verrà eseguita fino a quando non eseguirai il ciclo dei dati. È una query in attesa di essere necessaria (ovvero caricamento lento).

Se chiami ToList, la query verrà eseguita o "materializzata", come mi piace dire.

Ci sono pro e contro per entrambi. Se si chiama ToList, è possibile rimuovere alcuni dubbi su quando viene eseguita la query. Se ti attieni a IEnumerable, ottieni il vantaggio che il programma non fa alcun lavoro fino a quando non è effettivamente richiesto.


25

Condividerò un concetto abusato che mi è caduto in un giorno:

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));


// updating existing list
names[0] = "ford";

// Guess what should be printed before continuing
print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

Risultato atteso

// I was expecting    
print( startingWith_M.ToList() ); // mercedes, mazda
print( startingWith_F.ToList() ); // fiat, ferrari

Risultato attuale

// what printed actualy   
print( startingWith_M.ToList() ); // mazda
print( startingWith_F.ToList() ); // ford, fiat, ferrari

Spiegazione

Come per altre risposte, la valutazione del risultato è stata rinviata fino alla chiamata ToListo ad altri metodi di invocazione simili, ad esempioToArray .

Quindi posso riscrivere il codice in questo caso come:

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

// updating existing list
names[0] = "ford";

// before calling ToList directly
var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));

print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

Gioca in giro

https://repl.it/E8Ki/0


1
Ciò è dovuto ai metodi linq (estensione) che in questo caso provengono da IEnumerable dove creano solo una query ma non la eseguono (dietro le quinte vengono utilizzati gli alberi delle espressioni). In questo modo hai la possibilità di fare molte cose con quella query senza toccare i dati (in questo caso i dati nell'elenco). Il metodo List accetta la query preparata e la esegue sulla fonte di dati.
Bronek,

2
In realtà, ho letto tutte le risposte e la tua è stata quella che ho votato, perché indica chiaramente la differenza tra i due senza parlare specificamente di LINQ / SQL. È essenziale sapere tutto questo PRIMA di arrivare a LINQ / SQL. Ammirare.
BeemerGuy,

Questa è una differenza importante da spiegare, ma il tuo "risultato atteso" non è realmente previsto. Lo stai dicendo come se fosse una sorta di gotcha piuttosto che di design.
Neme,

@Neme, sì Era la mia aspettativa prima di capire come IEnumerablefunziona, ma ora non lo è più da quando so come;)
amd

15

Se tutto quello che vuoi fare è elencarli, usa il IEnumerable.

Attenzione, tuttavia, che cambiare la collezione originale da elencare è un'operazione pericolosa - in questo caso, vorrai ToList prima. Ciò creerà un nuovo elemento di elenco per ogni elemento in memoria, enumerando IEnumerablee quindi è meno performante se si enumera una sola volta, ma è più sicuro e talvolta i Listmetodi sono utili (ad esempio in accesso casuale).


1
Non sono sicuro che sia sicuro dire che generare un elenco significhi prestazioni inferiori.
Steven Sudit,

@ Steven: in effetti, come dicevano thecoop e Chris, a volte potrebbe essere necessario utilizzare un elenco. Nel mio caso, ho concluso che non lo è. @ Daren: cosa intendi con "questo creerà un nuovo elenco per ogni elemento in memoria"? Forse intendevi una "voce di elenco"? :: -).
Axonn,

@Axonn sì, elenco voci. fisso.
Daren Thomas,

@Steven Se hai intenzione di iterare sugli elementi in IEnumerable, quindi creare prima un elenco (e iterare su quello) significa che devi iterare sugli elementi due volte . Pertanto, a meno che non si desideri eseguire operazioni più efficienti nell'elenco, ciò significa prestazioni inferiori.
Daren Thomas,

3
@jerhewet: non è mai una buona idea modificare una sequenza che viene ripetuta. Accadranno cose brutte. Le astrazioni coleranno. I demoni entreranno nella nostra dimensione e causeranno il caos. Quindi sì, .ToList()aiuta qui;)
Daren Thomas

5

Oltre a tutte le risposte postate sopra, ecco i miei due centesimi. Esistono molti altri tipi diversi da List che implementano IEnumerable come ICollection, ArrayList ecc. Quindi, se abbiamo IEnumerable come parametro di qualsiasi metodo, possiamo trasferire qualsiasi tipo di raccolta alla funzione. Cioè possiamo avere un metodo per operare sull'astrazione e non su alcuna implementazione specifica.


1

Esistono molti casi (come un elenco infinito o un elenco molto grande) in cui IEnumerable non può essere trasformato in un elenco. Gli esempi più ovvi sono tutti i numeri primi, tutti gli utenti di Facebook con i loro dettagli o tutti gli articoli su ebay.

La differenza è che gli oggetti "Elenco" sono memorizzati "proprio qui e proprio ora", mentre gli oggetti "IEnumerable" funzionano "solo uno alla volta". Quindi, se sto esaminando tutti gli elementi su ebay, uno alla volta sarebbe qualcosa che anche un piccolo computer può gestire, ma ". ToList ()" mi farebbe sicuramente esaurire la memoria, non importa quanto grande fosse il mio computer. Nessun computer può da solo contenere e gestire una così grande quantità di dati.

[Modifica] - Inutile dire che non è "né questo né quello". spesso sarebbe logico usare sia un elenco che un IEnumerable nella stessa classe. Nessun computer al mondo potrebbe elencare tutti i numeri primi, perché per definizione ciò richiederebbe una quantità infinita di memoria. Ma potresti facilmente pensare a un class PrimeContainerche contiene un IEnumerable<long> primes, che per ovvi motivi contiene anche un SortedList<long> _primes. tutti i numeri primi calcolati finora. il primo primo da controllare verrebbe eseguito solo con i numeri primi esistenti (fino alla radice quadrata). In questo modo ottieni entrambi - numeri primi uno alla volta (IEnumerable) e una buona lista di "numeri primi finora", che è una buona approssimazione dell'intera lista (infinita).

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.