Wie funktioniert die folgende LINQ-Anweisung?

160

Wie funktioniert die folgende LINQ- Anweisung?

Hier ist mein Code:

var list = new List<int>{1,2,4,5,6};
var even = list.Where(m => m%2 == 0);
list.Add(8);
foreach (var i in even)
{
    Console.WriteLine(i);
}

Ausgabe: 2, 4, 6, 8

Warum nicht 2, 4, 6?

Atish Dipongkor - MVP
quelle
102
Das Ergebnis eines Abfrageausdrucks ist eine Abfrage, nicht die Ausführung der Abfrage.
Eric Lippert
6
Weitere Informationen finden Sie in der akzeptierten Antwort auf diese Frage .
Daniel
9
Sicherlich können Sie sich einen Titel vorstellen, der die Frage tatsächlich zusammenfasst.
Matt Ball
2
Meine Vermutung über die Downvotes (6 bis jetzt nicht meine) ist, dass sie den Fragentitel für zu allgemein halten, um eine gute Frage zu sein. Angesichts der Anzahl der positiven Stimmen und der Tatsache, dass Sie im Newsletter zur Top-Frage der Woche werden, müssen Sie sich keine allzu großen Sorgen machen.
Abel

Antworten:

235

Die Ausgabe erfolgt 2,4,6,8aufgrund einer verzögerten Ausführung .

Die Abfrage wird tatsächlich ausgeführt, wenn die Abfragevariable durchlaufen wird, nicht wenn die Abfragevariable erstellt wird. Dies wird als verzögerte Ausführung bezeichnet.

- Suprotim Agarwal, " Zurückgestellte vs sofortige Abfrageausführung in LINQ"

Es gibt eine andere Ausführung namens Sofortige Abfrageausführung, die zum Zwischenspeichern von Abfrageergebnissen hilfreich ist. Nochmals von Suprotim Agarwal:

Um die sofortige Ausführung einer Abfrage zu erzwingen, die keinen Singleton-Wert erzeugt, können Sie die Methode ToList(), ToDictionary(), ToArray(), Count(), Average()oder Max()für eine Abfrage oder Abfragevariable aufrufen . Diese werden als Konvertierungsoperatoren bezeichnet, mit denen Sie eine Kopie / Momentaufnahme des Ergebnisses erstellen können und auf die Sie so oft zugreifen können, wie Sie möchten, ohne die Abfrage erneut ausführen zu müssen.

Wenn die Ausgabe erfolgen soll 2,4,6, verwenden Sie .ToList():

var list = new List<int>{1,2,4,5,6};
var even = list.Where(m => m%2 == 0).ToList();
list.Add(8);
foreach (var i in even)
 {
    Console.WriteLine(i);
 }
Atish Dipongkor - MVP
quelle
8
Count (), Max (), Avg (), Sum () und wahrscheinlich andere Methoden, die die gesamte Liste berücksichtigen müssen, führen ebenfalls zu einer Auswertung der Abfrage.
Kenned
1
Ich habe oft darüber nachgedacht, beispielsweise 'filteredList' als Variable und nicht 'filterList ()' als Methode zu verwenden. Die Idee ist, dass Sie jedes Mal, wenn Sie möchten, dass die Liste gefiltert wird, darüber iterieren, anstatt eine Methode aufzurufen. Könnte eine interessante, wenn auch ungewöhnliche und in Bezug auf die Leistung möglicherweise unvollständige Methode sein.
Katana314
4
@Sebastian - Im Anschluss an @ Kenned Kommentar, .First(), .FirstOrDefault(), .Single()und .SingleOrDefault()auslösen auch die Auswertung der Abfrage.
Scotty.NET
4
Erstaunlich, wie Sie die Antwort in weniger als 30 Sekunden erhalten haben: D
MC
2
@MC Ich weiß nicht, warum Sie diese Frage stellen. Es wurde nicht die gesamte Antwort gegeben. Es wurde mehrmals bearbeitet.
Atish Dipongkor - MVP
11

Dies ist auf eine verzögerte Ausführung zurückzuführen, was bedeutet, dass die Berechnung des Ausdrucks erst ausgeführt wird, wenn er irgendwo benötigt wird. Dies verbessert die Leistung, wenn die Daten zu groß sind.

Sandeep Chauhan
quelle
3
Sie könnten das nuancieren, da dies auch bedeuten kann, dass Ihre teure Aufzählung mehrmals ausgeführt wird. In einem solchen Fall kann es sogar zu Leistungseinbußen kommen.
Grimasse der Verzweiflung
0

Der Grund dafür ist die verzögerte Ausführung Ihres Lambda-Ausdrucks. Die Abfrage wird ausgeführt, wenn Sie mit der Iteration in der foreach-Schleife beginnen.

Prateek Dhuper
quelle
11
Technisch gesehen ist es die verzögerte Ausführung des Iterators , nicht des Lambda .
D Stanley
0

Wenn Sie eine IEnumerable <> verwenden, die von LINQ erhalten wurde, wird nur eine Enumerator-Klasse erstellt, und die Iteration beginnt erst, wenn Sie sie in einem bestimmten Schritt verwenden.

Miguel
quelle
-1

Sie erhalten dieses Ergebnis aufgrund einer verzögerten Ausführung, was bedeutet, dass das Ergebnis erst beim ersten Zugriff ausgewertet wird.

Um es klarer zu machen, fügen Sie einfach 10 zur Liste am Ende Ihres Snipets hinzu und drucken Sie dann erneut. Sie erhalten keine 10 in der Ausgabe

     var list = new List<int>{1,2,4,5,6};
    var even = list.Where(m => m%2 == 0).Tolist();
    list.Add(8);
    foreach (var i in even)
    {
        Console.WriteLine(i);
    }
//new*
    list.Add(10);
    foreach (var i in even)
    {
        Console.WriteLine(i);
    }
Sandeep
quelle
Hast du das tatsächlich versucht? Ich komme 10in die Ausgabe.
Mark Hurd
guter Fang @MarkHurd ja hat nicht hinzugefügt .ToList (). bearbeitet den Beitrag jetzt sollte es erwartete Ausgabe geben. Meine Erwartung war, dass der Ausdruck nur ausgewertet wird, wenn Sie die Variable zum ersten Mal verwenden, aber es sieht so aus, als würde sie jedes
Mal
Jetzt wird es 8in keiner Ausgabe mehr enthalten sein.
Mark Hurd