Tips and Tricks: Deferred execution using LINQ

Few days ago I was watching the Scott Allen’s video where he shows some interesting tips and tricks and ways to optimize your LINQ code. The following blog refers to one of his first tips in the video, so I’ve tried some tests which I’m going to explain here.

This will be a basic scenario using LINQ. The idea is to show how and when does LINQ executes.

Lets take a look at the following code

//#1 - we asign a test list with some random values
List<int> sourceList = new List<int>() { 1, 4, 7, 9, 11, 18, 19, 20, 21, 25, 30 };

//#2 - result will be list of elements that
//satisfy the condition specified in WHERE clause
var result = (from list in sourceList
             where list % 2 == 0
             select list).ToList();

//#3 - lets add two more values
sourceList.Add(40);
sourceList.Add(41);

//#4 - print the values from result
foreach (var num in result)
{
    Response.Write(num + " ");
}

So, I’ve created a source list that contain some random number elements. Then, I’m querying the list using LINQ and put the result in a ‘result’ variable. As you can notice, I’ve surrounded the statement in parenthesis and have called on it the ToList() method. We all know what will this method do, it will simply convert the IEnumerable result into List. Ok, everything is fine and the ‘result’ will be of List type.

Once we move forward, we decide to add another values in the source list (40 and 41 in our example). The last part of the code is the foreach statement which just prints all the elements from result list in the web application. Since I’ve made condition to find the even numbers (using mod), the result will be: 4 18 20 30

That was expected, or not? I think yes since its logical and these are the values that were filtered from the source list and stored in the result list.

Ok, lets move with the same code so that we won’t convert the result from the query to List.

//#1 - we asign a test list with some random values
List<int> sourceList = new List<int>() { 1, 4, 7, 9, 11, 18, 19, 20, 21, 25, 30 };

//#2 - result will be IEnumerable            
var result = (from list in sourceList
              where list % 2 == 0
              select list);

//#3 - lets add two more values
sourceList.Add(40);
sourceList.Add(41);

//#4 - print the values from result
foreach (var num in result)
{
    Response.Write(num + " ");
}

Everything is the same except we don’t have the ToList() method. It means that the result variable won’t be of List type but it will remain IEnumerable. So, what’s the trick here?

The result now is: 4 18 20 30 40

Oops… 40? From where it came? We have added 40 to the source collection, not to the result collection and the result collection was created previously not after the code lines where we add new values to the source collection, then is this a wrong behavior or what?

It’s definitely NOT a wrong behavior. That’s actually a deferred execution because the IEnumerable is not enumerated all until you cal it explicitly to enumerate it, such as in FOREACH loop. The result variable holds the definition of the LINQ statement and the source list. If you look in debug mode, you will see this

If you try to see the Result View, here is what it says

“Expanding the Result View will enumerate the IEnumerable” – as I’ve said previously, the IEnumerable is not enumerated. So, if you click on the refresh-like icon – it will expand the Result View and you will see the result, however, the result is not enumerated on runtime, which means, if we add another elements in the source collection, these elements will be taken into consideration on the result collection. The result collection will be enumerated once it’s called for that. So, after we’ve added the 40 and 41 values to the source list, here is what values we have in the result:

You see that we now have 13 elements (previously we had 11) including the last two elements (40 and 41) added in the source list.

At last, once we call the foreach statement the result will be enumerated and that’s why 40 is included in the result now because the LINQ statement is executed once the result is enumerated and not in the time when the query is defined.

I hope this was an useful info for you.

Kind Regards,
Hajan

No Comments