IEnumerable vs List – Che cosa usare? Come funzionano?

Ho alcuni dubbi su come funzionano gli Enumerator e LINQ. Considera questi due semplici aspetti:

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

o

 IEnumerable 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 stessa non è così importante. Quello che voglio chiedere è questo:

 foreach (Animal animal in sel) { /*do stuff*/ } 
  1. Ho notato che se utilizzo IEnumerable , quando eseguo il debug e controllo “sel”, che in quel caso è l’IEnumerable, ha alcuni membri interessanti: “inner”, “outer”, “innerKeySelector” e “outerKeySelector”, questi ultimi 2 sembrano essere delegati. Il membro “interno” non ha istanze “Animali” in esso, ma piuttosto istanze “Specie”, che era molto strano per me. Il membro “esterno” contiene istanze “Animali”. Presumo che i due delegati determinino quali elementi entrano e cosa ne esce?

  2. Ho notato che se uso “Distinct”, “inner” contiene 6 elementi (questo non è corretto poiché solo 2 sono Distinct), ma “outer” contiene i valori corretti. Ancora una volta, probabilmente i metodi delegati determinano questo, ma questo è un po ‘più di quanto so su IEnumerable.

  3. Ancora più importante, quale delle due opzioni è la migliore per quanto riguarda le prestazioni?

La conversione Elenco male tramite .ToList() ?

O magari usando direttamente l’enumeratore?

Se puoi, per favore spiega anche un po ‘o getta alcuni link che spiegano questo uso di IEnumerable.

IEnumerable descrive il comportamento, mentre List è un’implementazione di quel comportamento. Quando si utilizza IEnumerable , si dà al compilatore la possibilità di posticipare il lavoro fino a un momento successivo, possibilmente ottimizzando lungo il percorso. Se usi ToList () costringi il compilatore a reificare immediatamente i risultati.

Ogni volta che impongo le espressioni LINQ, utilizzo IEnumerable , perché specificando solo il comportamento offro a LINQ la possibilità di posticipare la valutazione e possibilmente ottimizzare il programma. Ricordare in che modo LINQ non genera l’SQL per interrogare il database finché non lo si enumera? Considera questo:

 public IEnumerable AllSpotted() { return from a in Zoo.Animals where a.coat.HasSpots == true select a; } public IEnumerable Feline(IEnumerable sample) { return from a in sample where a.race.Family == "Felidae" select a; } public IEnumerable Canine(IEnumerable sample) { return from a in sample where a.race.Family == "Canidae" select a; } 

Ora hai un metodo che seleziona un campione iniziale (“AllSpotted”), più alcuni filtri. Quindi ora puoi fare questo:

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

Quindi è più veloce usare List su IEnumerable ? Solo se si desidera impedire che una query venga eseguita più di una volta. Ma è complessivamente migliore? Bene in quanto sopra, Leopardi e Iene vengono convertiti in singole query SQL ciascuno , e il database restituisce solo le righe rilevanti. Ma se avessimo restituito una lista da AllSpotted() , allora potrebbe funzionare più lentamente perché il database potrebbe restituire molti più dati di quelli effettivamente necessari, e noi sprechiamo cicli facendo il filtraggio nel client.

In un programma, potrebbe essere meglio rimandare la conversione della tua query a una lista fino alla fine, quindi se ho intenzione di enumerare attraverso Leopards e Hyenas più di una volta, farei questo:

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

Una class che implementa IEnumerable consente di utilizzare la syntax foreach .

Fondamentalmente ha un metodo per ottenere il prossimo object nella collezione. Non ha bisogno che tutta la collezione sia in memoria e non sa quanti oggetti ci sono dentro, foreach continua a ricevere il prossimo object fino a quando non finisce.

Questo può essere molto utile in determinate circostanze, ad esempio in una tabella di database enorme non si desidera copiare l’intera cosa in memoria prima di iniziare l’elaborazione delle righe.

Ora List implementa IEnumerable , ma rappresenta l’intera raccolta in memoria. Se si dispone di un object IEnumerable e si chiama .ToList() si crea una nuova lista con i contenuti dell’enumerazione in memoria.

L’espressione linq restituisce un’enumerazione e, per impostazione predefinita, l’espressione viene eseguita quando si esegue l’iterazione tramite il foreach . Un’istruzione IEumerable linq viene eseguita quando si itera il foreach , ma è ansible forzarlo per iterare prima usando .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 ) ... 

C’è un ottimo articolo scritto da: TechBlog di Claudio Bernasconi qui: Quando utilizzare IEnumerable, ICollection, IList e List

Ecco alcuni punti fondamentali su scenari e funzioni:

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

La cosa più importante da capire è che, usando Linq, la query non viene immediatamente valutata. Viene eseguito solo come parte dell’iterazione attraverso il risultante IEnumerable in un foreach – questo è ciò che fanno tutti i delegati strani.

Pertanto, il primo esempio valuta immediatamente la query chiamando ToList e inserendo i risultati della query in un elenco.
Il secondo esempio restituisce un IEnumerable che contiene tutte le informazioni necessarie per eseguire la query in seguito.

In termini di prestazioni, la risposta è che dipende . Se hai bisogno che i risultati vengano valutati contemporaneamente (per esempio, stai mutando le strutture che stai interrogando in seguito, o se non vuoi che l’iterazione su IEnumerable impieghi molto tempo) usi un elenco . Altrimenti usa un object IEnumerable . L’impostazione predefinita dovrebbe essere quella di utilizzare la valutazione su richiesta nel secondo esempio, in quanto generalmente utilizza meno memoria, a meno che non vi sia un motivo specifico per archiviare i risultati in un elenco.

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

IEnumerable è di sola lettura e List non lo è.

Vedi la differenza pratica tra List e IEnumerable

Il vantaggio di IEnumerable è l’esecuzione posticipata (di solito con i database). La query non verrà eseguita finché non si effettuerà il loop dei dati. È una query che aspetta fino a quando non è necessaria (ovvero carico pigro).

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

Ci sono pro e contro per entrambi. Se chiami ToList, puoi rimuovere alcuni misteri su quando la query viene eseguita. Se ci si attiene a IEnumerable, si ha il vantaggio che il programma non svolge alcun lavoro finché non è effettivamente necessario.

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

Attenzione, tuttavia, che la modifica della raccolta originale elencata è un’operazione pericolosa: in questo caso, per prima ToList necessario selezionare ToList . Questo creerà un nuovo elemento di lista per ogni elemento in memoria, enumerando l’object IEnumerable ed è quindi meno performante se si enumera solo una volta, ma più sicuro ea volte i metodi List sono a portata di mano (ad esempio in accesso casuale).

Condividerò un concetto abusato in cui sono caduto in un giorno:

 var names = new List {"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 le altre risposte, la valutazione del risultato è stata posticipata fino a chiamare ToList o metodi di chiamata simili, ad esempio ToArray .

Quindi posso riscrivere il codice in questo caso come:

 var names = new List {"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 vicino

https://repl.it/E8Ki/0

Oltre a tutte le risposte pubblicate sopra, ecco i miei due centesimi. Ci sono molti altri tipi oltre a List che implementa IEnumerable come ICollection, ArrayList ecc. Quindi se abbiamo IEnumerable come parametro di qualsiasi metodo, possiamo passare qualsiasi tipo di collezione alla funzione. Vale a dire, possiamo avere un metodo per operare sull’astrazione, non su un’implementazione specifica.