IEnumerable vs List - Was zu verwenden? Wie arbeiten Sie?


675

Ich habe einige Zweifel an der Funktionsweise von Enumeratoren und LINQ. Betrachten Sie diese beiden einfachen Auswahlmöglichkeiten:

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

oder

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

Ich habe die Namen meiner ursprünglichen Objekte geändert, damit dies wie ein allgemeineres Beispiel aussieht. Die Abfrage selbst ist nicht so wichtig. Was ich fragen möchte, ist Folgendes:

foreach (Animal animal in sel) { /*do stuff*/ }
  1. Mir ist aufgefallen, dass IEnumerablebeim Debuggen und Überprüfen von "sel", in diesem Fall IEnumerable, einige interessante Elemente angezeigt werden: "inner", "Outer", "innerKeySelector" und "OuterKeySelector". Diese beiden letzten Elemente werden angezeigt Delegierte sein. Das "innere" Mitglied enthält keine "Tier" -Instanzen, sondern "Spezies" -Instanzen, was für mich sehr seltsam war. Das "äußere" Mitglied enthält "Tier" -Instanzen. Ich gehe davon aus, dass die beiden Delegierten bestimmen, was rein und was raus geht.

  2. Ich habe festgestellt, dass bei Verwendung von "Distinct" das "Inner" 6 Elemente enthält (dies ist falsch, da nur 2 Distinct sind), das "Outer" jedoch die richtigen Werte enthält. Auch hier bestimmen wahrscheinlich die delegierten Methoden dies, aber dies ist etwas mehr als ich über IEnumerable weiß.

  3. Welche der beiden Optionen ist vor allem in Bezug auf die Leistung die beste?

Die böse Listenkonvertierung über .ToList()?

Oder vielleicht den Enumerator direkt benutzen?

Wenn Sie können, erklären Sie bitte auch ein wenig oder werfen Sie einige Links, die diese Verwendung von IEnumerable erklären.

Antworten:


737

IEnumerablebeschreibt das Verhalten, während List eine Implementierung dieses Verhaltens ist. Wenn Sie verwenden IEnumerable, geben Sie dem Compiler die Möglichkeit, die Arbeit bis später zu verschieben und möglicherweise auf dem Weg zu optimieren. Wenn Sie ToList () verwenden, zwingen Sie den Compiler, die Ergebnisse sofort zu überprüfen.

Immer wenn ich LINQ-Ausdrücke "staple", verwende ich IEnumerable, weil ich durch Angabe des Verhaltens LINQ die Möglichkeit gebe, die Auswertung zu verschieben und möglicherweise das Programm zu optimieren. Denken Sie daran, dass LINQ das SQL zum Abfragen der Datenbank erst generiert, wenn Sie es auflisten. Bedenken Sie:

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;
}

Jetzt haben Sie eine Methode, die ein erstes Beispiel auswählt ("AllSpotted"), sowie einige Filter. Jetzt können Sie Folgendes tun:

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

Ist es also schneller, List over zu verwenden IEnumerable? Nur wenn Sie verhindern möchten, dass eine Abfrage mehrmals ausgeführt wird. Aber ist es insgesamt besser? Nun, oben werden Leoparden und Hyänen in einzelne SQL-Abfragen konvertiert , und die Datenbank gibt nur die relevanten Zeilen zurück. Wenn wir jedoch eine Liste von zurückgegeben haben AllSpotted(), läuft diese möglicherweise langsamer, da die Datenbank weitaus mehr Daten zurückgeben kann, als tatsächlich benötigt wird, und wir Zyklen mit der Filterung im Client verschwenden.

In einem Programm ist es möglicherweise besser, die Konvertierung Ihrer Abfrage in eine Liste bis zum Ende zu verschieben. Wenn ich also mehr als einmal durch Leoparden und Hyänen aufzählen möchte, würde ich Folgendes tun:

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

11
Ich denke, sie beziehen sich auf die beiden Seiten eines Joins. Wenn Sie "SELECT * FROM Animals JOIN Species ..." ausführen, ist der innere Teil des Joins Animals und der äußere Teil Species.
Chris Wenham

10
Als ich die Antworten zu folgenden Themen gelesen habe: IEnumerable <T> vs IQueryable <T> habe ich die analoge Erklärung gesehen, sodass IEnumerable die Laufzeit automatisch dazu zwingt, LINQ to Objects zum Abfragen der Sammlung zu verwenden. Also bin ich zwischen diesen 3 Typen verwirrt. stackoverflow.com/questions/2876616/…
Bronek

4
@Bronek Die Antwort, die Sie verlinkt haben, ist wahr. IEnumerable<T>wird nach dem ersten Teil LINQ-To-Objects sein, was bedeutet, dass alle entdeckten Objekte zurückgegeben werden müssten, um Feline auszuführen. Auf der anderen Seite IQuertable<T>kann die Abfrage verfeinert werden, wobei nur Spotted Felines heruntergezogen werden.
Nate

21
Diese Antwort ist sehr irreführend! @ Nates Kommentar erklärt warum. Wenn Sie IEnumerable <T> verwenden, wird der Filter auf der Clientseite ausgeführt, egal was passiert.
Hans

5
Ja, AllSpotted () würde zweimal ausgeführt. Das größere Problem bei dieser Antwort ist die folgende Aussage: "Oben werden Leoparden und Hyänen in einzelne SQL-Abfragen konvertiert, und die Datenbank gibt nur die relevanten Zeilen zurück." Dies ist falsch, da die where-Klausel für eine IEnumerable <> aufgerufen wird und nur Objekte durchlaufen können, die bereits aus der Datenbank stammen. Wenn Sie AllSpotted () und die Parameter Feline () und Canine () in IQueryable zurückgeben würden, würde der Filter in SQL erfolgen und diese Antwort wäre sinnvoll.
Hans

178

Es gibt einen sehr guten Artikel von: Claudio Bernasconis TechBlog hier: Wann man IEnumerable, ICollection, IList und List verwendet

Hier einige grundlegende Punkte zu Szenarien und Funktionen:

Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein


25
Es sollte darauf hingewiesen werden, dass dieser Artikel nur für öffentlich zugängliche Teile Ihres Codes bestimmt ist, nicht für die internen Abläufe. Listist eine Implementierung von IListund hat als solche eine zusätzliche Funktionalität auf der in IList(z Sort, Find, InsertRange). Wenn Sie sich selbst Gewalt anzuwenden , um IListüber List, lösen Sie diese Methoden , die Sie benötigen
Jonathan Twite

4
Vergiss nichtIReadOnlyCollection<T>
Dandré

2
Es kann hilfreich sein, auch hier ein einfaches Array []einzufügen.
6.

Auch wenn es verpönt sein mag, danke, dass Sie diese Grafik und diesen Artikel geteilt haben
Daniel

133

Mit einer implementierten Klasse IEnumerablekönnen Sie die foreachSyntax verwenden.

Grundsätzlich gibt es eine Methode, um das nächste Element in der Sammlung zu erhalten. Es muss nicht die gesamte Sammlung gespeichert werden und es ist nicht bekannt, wie viele Elemente sich darin befinden. Es wird foreachnur das nächste Element abgerufen, bis es aufgebraucht ist.

Dies kann unter bestimmten Umständen sehr nützlich sein, z. B. in einer massiven Datenbanktabelle, in der Sie nicht das gesamte Objekt in den Speicher kopieren möchten, bevor Sie mit der Verarbeitung der Zeilen beginnen.

ListImplementiert jetzt IEnumerable, repräsentiert aber die gesamte Sammlung im Speicher. Wenn Sie eine haben IEnumerableund Sie anrufen .ToList(), erstellen Sie eine neue Liste mit dem Inhalt der Aufzählung im Speicher.

Ihr linq-Ausdruck gibt eine Aufzählung zurück, und der Ausdruck wird standardmäßig ausgeführt, wenn Sie die Verwendung von durchlaufen foreach. Eine IEnumerablelinq-Anweisung wird ausgeführt, wenn Sie die iterieren. foreachSie können sie jedoch zwingen, sie früher zu iterieren .ToList().

Folgendes meine ich:

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
Aber was passiert, wenn Sie einen foreach auf einem IEnumerable ausführen, ohne ihn zuerst in eine Liste zu konvertieren ? Bringt es die gesamte Sammlung in Erinnerung? Oder instanziiert es das Element einzeln, während es über die foreach-Schleife iteriert? danke
Pap

@Pap Letzteres: Es wird erneut ausgeführt, nichts wird automatisch im Speicher zwischengespeichert.
Keith

Es scheint, als ob der Schlüsseldifferenz 1) das Ganze im Gedächtnis ist oder nicht. 2) IEnumerable lassen Sie mich verwenden, foreachwährend Liste sagen Index geht. Wenn ich die Anzahl / Länge von thingvorher wissen möchte , hilft IEnumerable nicht, oder?
Jeb50

@ Jeb50 Nicht genau - beides Listund Arrayimplementieren IEnumerable. Sie können sich IEnumerableeinen kleinsten gemeinsamen Nenner vorstellen, der sowohl für Speichersammlungen als auch für große Sammlungen funktioniert, die jeweils ein Element erhalten. Wenn Sie anrufen, rufen IEnumerable.Count()Sie möglicherweise eine schnelle .LengthEigenschaft auf oder gehen die gesamte Sammlung durch - der Punkt ist, dass IEnumerableSie es nicht wissen. Das kann ein Problem sein, aber wenn Sie nur dazu gehen, ist foreaches Ihnen egal - Ihr Code funktioniert mit einem Arrayoder DataReaderdemselben.
Keith

1
@MFouadKajj Ich weiß nicht, welchen Stapel Sie verwenden, aber es wird mit ziemlicher Sicherheit nicht mit jeder Zeile eine Anfrage gestellt. Der Server führt die Abfrage aus und berechnet den Startpunkt der Ergebnismenge, erhält jedoch nicht das Ganze. Bei kleinen Ergebnismengen handelt es sich wahrscheinlich um eine einzelne Reise. Bei großen Ergebnissen senden Sie eine Anfrage nach mehr Zeilen aus den Ergebnissen, aber es wird nicht die gesamte Abfrage erneut ausgeführt.
Keith

97

Niemand erwähnte einen entscheidenden Unterschied, der ironischerweise auf eine Frage beantwortet wurde, die als Duplikat davon geschlossen wurde.

IEnumerable ist schreibgeschützt und List nicht.

Siehe Praktischer Unterschied zwischen List und IEnumerable


Liegt das im Anschluss an dem Interface-Aspekt oder am List-Aspekt? dh ist IList auch schreibgeschützt?
Jason Masters

IList ist nicht schreibgeschützt - docs.microsoft.com/en-us/dotnet/api/… IEnumerable ist schreibgeschützt, da es keine Methoden zum Hinzufügen oder Entfernen von Elementen gibt, sobald es erstellt wurde. Es ist eine der Basisschnittstellen, die IList erweitert (siehe Link)
CAD-Typ

67

Das Wichtigste ist, dass die Abfrage mit Linq nicht sofort ausgewertet wird. Es wird nur als Teil des Durchlaufens des resultierenden IEnumerable<T>a ausgeführt foreach- das ist es, was alle seltsamen Delegierten tun.

Im ersten Beispiel wird die Abfrage sofort ausgewertet, ToListindem die Abfrageergebnisse aufgerufen und in eine Liste aufgenommen werden.
Das zweite Beispiel gibt eine zurück IEnumerable<T>, die alle Informationen enthält, die zum späteren Ausführen der Abfrage erforderlich sind.

In Bezug auf die Leistung ist die Antwort, dass es darauf ankommt . Wenn die Ergebnisse sofort ausgewertet werden sollen (z. B. wenn Sie die Strukturen, die Sie später abfragen, mutieren oder wenn die Iteration nicht IEnumerable<T>lange dauern soll), verwenden Sie eine Liste. Andernfalls verwenden Sie eine IEnumerable<T>. Standardmäßig sollte die On-Demand-Auswertung im zweiten Beispiel verwendet werden, da diese im Allgemeinen weniger Speicher benötigt, es sei denn, es gibt einen bestimmten Grund, die Ergebnisse in einer Liste zu speichern.


Hallo und danke für die Antwort :: -). Dies räumte fast alle meine Zweifel auf. Irgendeine Idee, warum die Aufzählung in "innere" und "äußere" "aufgeteilt" wird? Dies passiert, wenn ich das Element im Debug / Break-Modus per Maus inspiziere. Ist das vielleicht der Beitrag von Visual Studio? Vor Ort aufzählen und Eingabe und Ausgabe der Aufzählung anzeigen?
Axonn

5
Das ist die JoinArbeit - innen und außen sind die beiden Seiten der Verbindung. Machen Sie sich im Allgemeinen keine Sorgen darüber, was tatsächlich im IEnumerablesCode enthalten ist, da dieser sich vollständig von Ihrem eigentlichen Code unterscheidet. Sorgen Sie sich nur um die tatsächliche Ausgabe, wenn Sie darüber iterieren :)
Thecoop

40

Der Vorteil von IEnumerable ist die verzögerte Ausführung (normalerweise mit Datenbanken). Die Abfrage wird erst ausgeführt, wenn Sie die Daten tatsächlich durchlaufen haben. Es ist eine Abfrage, die wartet, bis sie benötigt wird (auch bekannt als verzögertes Laden).

Wenn Sie ToList aufrufen, wird die Abfrage ausgeführt oder "materialisiert", wie ich gerne sagen möchte.

Beide haben Vor- und Nachteile. Wenn Sie ToList aufrufen, können Sie einige Rätsel lösen, wann die Abfrage ausgeführt wird. Wenn Sie sich an IEnumerable halten, haben Sie den Vorteil, dass das Programm erst dann arbeitet, wenn es tatsächlich benötigt wird.


25

Ich werde ein missbrauchtes Konzept teilen, in das ich eines Tages geraten bin:

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() );

Erwartetes Ergebnis

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

Tatsächliche Ergebnis

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

Erläuterung

Wie aus anderen Antworten hervorgeht, wurde die Auswertung des Ergebnisses bis zum Aufruf ToListoder ähnlichen Aufrufmethoden verschoben ToArray.

Daher kann ich den Code in diesem Fall wie folgt umschreiben:

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() );

Spielen Sie herum

https://repl.it/E8Ki/0


1
Dies liegt an linq-Methoden (Erweiterung), die in diesem Fall von IEnumerable stammen und nur eine Abfrage erstellen, aber nicht ausführen (hinter den Kulissen werden die Ausdrucksbäume verwendet). Auf diese Weise haben Sie die Möglichkeit, mit dieser Abfrage viele Dinge zu tun, ohne die Daten zu berühren (in diesem Fall Daten in der Liste). Die List-Methode nimmt die vorbereitete Abfrage und führt sie für die Datenquelle aus.
Bronek

2
Eigentlich habe ich alle Antworten gelesen, und Ihre war die, die ich hochgestimmt habe, weil sie den Unterschied zwischen den beiden deutlich macht, ohne speziell über LINQ / SQL zu sprechen. Es ist wichtig, dies alles zu wissen, bevor Sie zu LINQ / SQL gelangen. Bewundern.
BeemerGuy

Das ist ein wichtiger Unterschied, aber Ihr "erwartetes Ergebnis" wird nicht wirklich erwartet. Du sagst es so, als wäre es eher eine Art Gotcha als Design.
Neme

@Neme, ja Es war meine Erwartung, bevor ich verstehe, wie es IEnumerablefunktioniert, aber jetzt ist nicht mehr, da ich weiß wie;)
amd

15

Wenn Sie sie nur aufzählen möchten, verwenden Sie die IEnumerable.

Beachten Sie jedoch, dass das Ändern der aufgezählten Originalsammlung eine gefährliche Operation ist. In diesem Fall sollten Sie dies zuerst tun ToList. Dadurch wird für jedes Element im Speicher ein neues Listenelement erstellt, das das Element auflistet, IEnumerableund es ist daher weniger leistungsfähig, wenn Sie nur einmal auflisten - aber sicherer und manchmal sind die ListMethoden praktisch (z. B. bei wahlfreiem Zugriff).


1
Ich bin mir nicht sicher, ob das Generieren einer Liste eine geringere Leistung bedeutet.
Steven Sudit

@ Steven: In der Tat, wie thecoop und Chris sagten, kann es manchmal notwendig sein, eine Liste zu verwenden. In meinem Fall bin ich zu dem Schluss gekommen, dass dies nicht der Fall ist. @ Daren: Was meinst du mit "Dadurch wird eine neue Liste für jedes Element im Speicher erstellt"? Vielleicht meintest du einen "Listeneintrag"? :: -).
Axonn

@Axonn ja, ich erwähne Listeneintrag. Fest.
Daren Thomas

@Steven Wenn Sie vorhaben, über die Elemente in der zu IEnumerableiterieren, bedeutet das erstmalige Erstellen einer Liste (und das Iterieren darüber), dass Sie die Elemente zweimal durchlaufen . Wenn Sie also keine Operationen ausführen möchten, die auf der Liste effizienter sind, bedeutet dies wirklich eine geringere Leistung.
Daren Thomas

3
@ jerhewet: Es ist nie eine gute Idee, eine Sequenz zu ändern, über die iteriert wird. Es werden schlimme Dinge passieren. Abstraktionen werden auslaufen. Dämonen werden in unsere Dimension eindringen und Chaos anrichten. Also ja, .ToList()hilft hier;)
Daren Thomas

5

Zusätzlich zu allen oben genannten Antworten sind hier meine zwei Cent. Es gibt viele andere Typen als List, die IEnumerable wie ICollection, ArrayList usw. implementieren. Wenn wir also IEnumerable als Parameter einer Methode haben, können wir alle Auflistungstypen an die Funktion übergeben. Das heißt, wir können eine Methode haben, um mit der Abstraktion zu arbeiten, keine spezifische Implementierung.


1

Es gibt viele Fälle (z. B. eine unendliche Liste oder eine sehr große Liste), in denen IEnumerable nicht in eine Liste umgewandelt werden kann. Die offensichtlichsten Beispiele sind alle Primzahlen, alle Benutzer von Facebook mit ihren Details oder alle Artikel bei ebay.

Der Unterschied besteht darin, dass "List" -Objekte "genau hier und genau jetzt" gespeichert werden, während "IEnumerable" -Objekte "nur einzeln" funktionieren. Wenn ich also alle Artikel bei ebay durchgehen würde, wäre es etwas, mit dem selbst ein kleiner Computer umgehen kann, aber ".ToList ()" würde mir sicherlich den Speicher ausgehen, egal wie groß mein Computer war. Kein Computer kann für sich genommen so viele Daten enthalten und verarbeiten.

[Bearbeiten] - Unnötig zu sagen - es ist nicht "entweder dies oder das". Oft ist es sinnvoll, sowohl eine Liste als auch eine IEnumerable in derselben Klasse zu verwenden. Kein Computer auf der Welt könnte alle Primzahlen auflisten, da dies per Definition unendlich viel Speicher erfordern würde. Aber man könnte sich leicht ein class PrimeContainervorstellen IEnumerable<long> primes, das ein enthält , das aus offensichtlichen Gründen auch ein enthält SortedList<long> _primes. alle bisher berechneten Primzahlen. Die nächste zu prüfende Primzahl würde nur gegen die vorhandenen Primzahlen (bis zur Quadratwurzel) ausgeführt. Auf diese Weise erhalten Sie sowohl Primzahlen einzeln (IEnumerable) als auch eine gute Liste von "Primzahlen bisher", was eine ziemlich gute Annäherung an die gesamte (unendliche) Liste darstellt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.