160

How does the following LINQ statement work?

Here is my 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);
}

Output: 2, 4, 6, 8

Why not 2, 4, 6?

Atish Kumar Dipongkor
  • 10,220
  • 9
  • 49
  • 77
  • 102
    The result of a query expression is a query, not the execution of the query. – Eric Lippert Jul 17 '13 at 14:41
  • 6
    For less information see the accepted answer to [this question](http://stackoverflow.com/q/215548/162396). – Daniel Jul 17 '13 at 14:45
  • 9
    Surely you can think of a title which actually summarizes the question. – Matt Ball Jul 17 '13 at 16:51
  • 2
    My guess about the downvotes (6 by now, not mine) is that they consider the question title too generic to be a good question. But, seeing the number of upvotes, and becoming the top question of the week in the newsletter, I don't think you need to worry about it too much. – Abel Jul 23 '13 at 17:50

5 Answers5

235

The output is 2,4,6,8 because of deferred execution.

The query is actually executed when the query variable is iterated over, not when the query variable is created. This is called deferred execution.

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

There is another execution called Immediate Query Execution, which is useful for caching query results. From Suprotim Agarwal again:

To force immediate execution of a query that does not produce a singleton value, you can call the ToList(), ToDictionary(), ToArray(), Count(), Average() or Max() method on a query or query variable. These are called conversion operators which allow you to make a copy/snapshot of the result and access is as many times you want, without the need to re-execute the query.

If you want the output to be 2,4,6, use .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 Kumar Dipongkor
  • 10,220
  • 9
  • 49
  • 77
  • Would it only be executed when iterating or in other cases, too? – Sebastian Jul 17 '13 at 13:57
  • 8
    Count(), Max(), Avg(), Sum() and probably other methods that need to take the whole list into consideration, also cause evaluation of the query. – Kenned Jul 17 '13 at 14:35
  • 1
    I've often thought about having, say, 'filteredList' as a variable, rather than 'filterList()' as a method - the idea being, you iterate over it each time you want the list filtered, rather than calling a method. Might be an interesting, if unusual and perhaps imperfect performance-wise, method of doing things. – Katana314 Jul 17 '13 at 14:41
  • 4
    @Sebastian - Further to @Kenned's comment, `.First()`, `.FirstOrDefault()`, `.Single()` and `.SingleOrDefault()` also trigger the evaluation of the query. – Scotty.NET Jul 17 '13 at 15:46
  • In this code example and the one in the question, is `even` of the same type? – Cory Klein Jul 17 '13 at 17:53
  • So, if at the end of the code without the ToList() we add `list.Add(10)` and then a second loop, is the output `2, 4, 6, 8, 2, 4, 6, 8` or `2, 4, 6, 8, 2, 4, 6, 8, 10`? In other words, its it executed each time it is iterated over or only the first time? – Lawtonfogle Jul 17 '13 at 18:14
  • 1
    @Lawtonfogle Why don't you try it and find out? You already have the exact code you need to run, just run it. – Servy Jul 17 '13 at 18:28
  • @Servy You assume I currently have access to a machine that can try it. My current machine is set up for Oracle Forms (though I'd love to be doing my current work in .Net instead). – Lawtonfogle Jul 17 '13 at 18:41
  • Very true. A LINQ query is not executed until it has been iterated over. – Jay Patel Jul 17 '13 at 20:37
  • 4
    astonishing how you got the answer in less than 30sec :D – M.C. Jul 17 '13 at 21:30
  • 1
    @Lawtonfogle: It would be `2, 4, 6, 8, 2, 4, 6, 8, 10`. `even` becomes like a "function" in that it gets called every time you iterate over it. You could even use a variable in place of the 2 and change that each time. – Cemafor Jul 17 '13 at 21:57
  • 2
    @M.C I don't know why you are asking this question. Whole answer was not given at a time. It was edited several times. – Atish Kumar Dipongkor Jul 18 '13 at 02:50
  • 1
    @Atish-Dipongkor Nice rep jump for a SO-"newbie" :D – Grimace of Despair Jul 18 '13 at 10:50
  • This poster was suspended for plagiarism? I hope it wasn't for this post - a quick look at the history shows he *did* properly cite the original source, he just apparently didn't know how to use the quote-feature correctly. – BlueRaja - Danny Pflughoeft Jul 23 '13 at 22:25
11

This has happened because of deferred execution, which means that the calculation of the expression is not executed until it is needed someplace. This makes the performance better if the data is too large.

Steve
  • 6,334
  • 4
  • 39
  • 67
Sandeep Chauhan
  • 1,313
  • 1
  • 10
  • 23
  • 3
    You might nuance that, as it can also mean that your expensive enumeration is being executed multiple times. In such a case you might even suffer performance loss. – Grimace of Despair Jul 18 '13 at 10:48
0

The reason for this is the deferred execution of your lambda expression. The query gets executed when you start iterating in the foreach loop.

lesderid
  • 3,388
  • 8
  • 39
  • 65
Prateek Dhuper
  • 51
  • 1
  • 1
  • 7
0

When you use an IEnumerable<> obtained from LINQ, only is created an Enumerator class and the iteration only start when you use it in some walk.

Miguel
  • 3,786
  • 2
  • 19
  • 32
-1

You are getting this result because of deferred execution which means result is actually not evaluated until its first accessed.

To make it more clear just add 10 to the list at end of your snipet and then print again you will not get 10 in output

     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
  • 996
  • 2
  • 11
  • 22
  • Did you actually try that? I get `10` in the output. – Mark Hurd Jul 25 '13 at 06:44
  • good catch @MarkHurd yes didn't added .ToList(). edited the post now it should give expected output. My expectation was expression is evaluated only when you use the var for first time but it looks like it is getting evaluated everytime – sandeep Jul 31 '13 at 08:17
  • Now it won't contain `8` in either output. – Mark Hurd Jul 31 '13 at 11:40