Wenn ich so etwas schreibe:
var things = mythings
.Where(x => x.IsSomeValue)
.Where(y => y.IsSomeOtherValue)
Ist das dasselbe wie:
var results1 = new List<Thing>();
foreach(var t in mythings)
if(t.IsSomeValue)
results1.Add(t);
var results2 = new List<Thing>();
foreach(var t in results1)
if(t.IsSomeOtherValue)
results2.Add(t);
Oder gibt es etwas Magie unter der Decke, die eher so funktioniert:
var results = new List<Thing>();
foreach(var t in mythings)
if(t.IsSomeValue && t.IsSomeOtherValue)
results.Add(t);
Oder ist es etwas ganz anderes?
Antworten:
LINQ-Abfragen sind faul . Das heißt der Code:
tut sehr wenig. Das ursprüngliche enumerable (
mythings
) wird nur dann aufgezählt, wenn das resultierende enumerable (things
) verbraucht wird, z. B. von einerforeach
Schleife.ToList()
, oder.ToArray()
.Wenn Sie aufrufen
things.ToList()
, entspricht dies in etwa dem zuletzt genannten Code, möglicherweise mit einigem (normalerweise unbedeutendem) Overhead durch die Enumeratoren.Ebenso, wenn Sie eine foreach-Schleife verwenden:
Es ist in der Leistung ähnlich zu:
Einige der Leistungsvorteile des Laziness-Ansatzes für Enumerables (im Gegensatz zum Berechnen aller Ergebnisse und zum Speichern in einer Liste) bestehen darin, dass nur sehr wenig Arbeitsspeicher belegt wird (da jeweils nur ein Ergebnis gespeichert wird) und keine signifikanten Fehler auftreten Kosten.
Wenn die Aufzählung nur teilweise erfolgt, ist dies besonders wichtig. Betrachten Sie diesen Code:
Die Art und Weise, wie LINQ implementiert wird,
mythings
wird nur bis zum ersten Element aufgezählt, das Ihren where-Bedingungen entspricht. Befindet sich dieses Element zu Beginn der Liste, kann dies eine enorme Leistungssteigerung bedeuten (z. B. O (1) anstelle von O (n)).quelle
foreach
besteht darin, dass LINQ Delegate-Aufrufe verwendet, die einen gewissen Overhead haben. Dies kann von Bedeutung sein, wenn die Bedingungen sehr schnell ausgeführt werden (was häufig der Fall ist).ToList
oderToArray
. Wenn so etwas richtig eingebaut worden wäreIEnumerable
, wäre es möglich gewesen, eine Liste anzufordern, um alle Aspekte, die sich in der Zukunft ändern könnten, zu "schnappschießen", ohne alles generieren zu müssen.Der folgende Code:
Ist gleichbedeutend mit nichts, wegen der faulen Bewertung wird nichts passieren.
Ist anders, weil die Auswertung gestartet wird.
Jeder Gegenstand
mythings
wird dem ersten gegebenWhere
. Wenn es besteht, wird es auf die Sekunde gegebenWhere
. Wenn es erfolgreich ist, wird es Teil der Ausgabe.Das sieht also ungefähr so aus:
quelle
Abgesehen von der verzögerten Ausführung (auf die in den anderen Antworten bereits eingegangen wird, ich möchte nur auf ein weiteres Detail hinweisen) ist dies eher in Ihrem zweiten Beispiel der Fall.
Stellen wir uns vor, Sie rufen
ToList
anthings
.Die Umsetzung von
Enumerable.Where
Retouren aEnumerable.WhereListIterator
. Wenn Sie rufenWhere
auf , dassWhereListIterator
(auch bekannt als VerkettungsWhere
-calls), die Sie nicht mehr AnrufEnumerable.Where
, aberEnumerable.WhereListIterator.Where
, die die Prädikate tatsächlich kombiniert (mitEnumerable.CombinePredicates
).Also ist es eher so
if(t.IsSomeValue && t.IsSomeOtherValue)
.quelle
Nein, es ist nicht dasselbe. In Ihrem Beispiel
things
ist einIEnumerable
, der zu diesem Zeitpunkt noch nur ein Iterator ist, kein tatsächliches Array oder eine Liste. Darüber hinausthings
wird die Schleife niemals ausgewertet, da sie nicht verwendet wird. Der TypIEnumerable
ermöglicht es, Elemente, dieyield
von Linq-Anweisungen erstellt wurden, zu durchlaufen und mit weiteren Anweisungen weiterzuverarbeiten, was bedeutet, dass Sie letztendlich nur eine einzige Schleife haben.Sobald Sie jedoch eine Anweisung wie
.ToArray()
oder hinzufügen.ToList()
, ordnen Sie die Erstellung einer tatsächlichen Datenstruktur an und setzen so Ihrer Kette Grenzen.Siehe diese verwandte SO-Frage: /programming/2789389/how-do-i-implement-ienumerable
quelle