Jak działa następująca instrukcja LINQ?

160

Jak działa następująca instrukcja LINQ ?

Oto mój kod:

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

Wynik: 2, 4, 6, 8

Dlaczego nie 2, 4, 6?

Atish Dipongkor - MVP
źródło
102
Wynikiem wyrażenia zapytania jest zapytanie, a nie wykonanie zapytania.
Eric Lippert,
6
Aby uzyskać mniej informacji, zobacz zaakceptowaną odpowiedź na to pytanie .
Daniel,
9
Z pewnością możesz pomyśleć o tytule, który faktycznie podsumowuje pytanie.
Matt Ball,
2
Moje przypuszczenie co do głosów przeciwnych (do tej pory 6, nie moich) jest takie, że uważają tytuł pytania za zbyt ogólny, aby był dobrym pytaniem. Ale widząc liczbę głosów za i stając się głównym pytaniem tygodnia w biuletynie, nie sądzę, żebyś musiał się tym zbytnio przejmować.
Abel

Odpowiedzi:

235

Dane wyjściowe są 2,4,6,8spowodowane odroczonym wykonaniem .

Zapytanie jest faktycznie wykonywane podczas iteracji zmiennej zapytania, a nie podczas tworzenia zmiennej zapytania. Nazywa się to odroczonym wykonaniem.

- Suprotim Agarwal, Deferred vs Immediate Query Execution in LINQ”

Istnieje inne wykonanie o nazwie Immediate Query Execution , które jest przydatne do buforowania wyników zapytania. Z Suprotim Agarwal ponownie:

Aby wymusić natychmiastowe wykonanie zapytania, które nie generuje wartości pojedynczej, można wywołać metodę ToList(), ToDictionary(), ToArray(), Count(), Average()lub Max()w kwerendzie lub zmiennej kwerendy. Są to tak zwane operatory konwersji, które umożliwiają wykonanie kopii / migawki wyniku, a dostęp jest możliwy tyle razy, ile chcesz, bez konieczności ponownego wykonywania zapytania.

Jeśli chcesz, aby wynik był 2,4,6, użyj .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
źródło
8
Count (), Max (), Avg (), Sum () i prawdopodobnie inne metody, które wymagają uwzględnienia całej listy, również powodują ocenę zapytania.
Kenned
1
Często myślałem o, powiedzmy, „filterList” jako zmiennej, a nie „filterList ()” jako metodzie - chodzi o to, że za każdym razem, gdy chcesz przefiltrować listę, zamiast wywoływać metodę, będziesz ją powtarzał. Może być interesującą, choć niezwykłą i być może niedoskonałą pod względem wydajności metodą robienia rzeczy.
Katana314,
4
@Sebastian - W nawiązaniu do komentarza @ Kenned za, .First(), .FirstOrDefault(), .Single()a .SingleOrDefault()także wywołać ocenę zapytania.
Scotty.NET,
4
zadziwiające, jak otrzymałeś odpowiedź w mniej niż 30 sekund: D
MC
2
@MC Nie wiem, dlaczego zadajesz to pytanie. Nie udzielono jednocześnie pełnej odpowiedzi. Był kilkakrotnie redagowany.
Atish Dipongkor - MVP
11

Stało się tak z powodu odroczonego wykonania, co oznacza, że ​​obliczenie wyrażenia nie jest wykonywane, dopóki nie jest potrzebne gdzieś. To poprawia wydajność, jeśli dane są zbyt duże.

Sandeep Chauhan
źródło
3
Możesz to zniwelować, ponieważ może to również oznaczać, że twoje drogie wyliczenie jest wykonywane wiele razy. W takim przypadku możesz nawet stracić wydajność.
Grimace of Despair,
0

Powodem tego jest odroczone wykonanie wyrażenia lambda. Zapytanie jest wykonywane po rozpoczęciu iteracji w pętli foreach.

Prateek Dhuper
źródło
11
Z technicznego punktu widzenia jest to odroczone wykonanie iteratora , a nie lambda .
D Stanley,
0

W przypadku korzystania z IEnumerable <> uzyskanego z LINQ jest tworzona tylko klasa Enumerator, a iteracja rozpoczyna się tylko wtedy, gdy używasz jej w jakimś spacerze.

Miguel
źródło
-1

Otrzymujesz ten wynik z powodu odroczonego wykonania, co oznacza, że ​​wynik nie jest w rzeczywistości oceniany do momentu pierwszego dostępu.

Aby było to bardziej zrozumiałe, po prostu dodaj 10 do listy na końcu swojego snipeta i wydrukuj ponownie, nie otrzymasz 10 na wyjściu

     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
źródło
Naprawdę tego próbowałeś? Dostaję się 10na wyjściu.
Mark Hurd,
dobry chwyt @MarkHurd tak nie dodano .ToList (). edytował post teraz powinien dać oczekiwany wynik. Moje oczekiwanie to wyrażenie jest oceniane tylko wtedy, gdy używasz zmiennej var po raz pierwszy, ale wygląda na to, że jest ona oceniana za każdym razem
sandeep
Teraz nie będzie zawierać 8żadnego wyjścia.
Mark Hurd