Thursday, April 25, 2024 04:58

Table of contents >> LINQ > Deferred execution

Deferred execution

A lot of people seem to think that deferred execution exists because of LINQ, and that is only partially true. LINQ does have a lot to do with it, but deferred execution exists first of all because of the yield statement.

But I’m talking about deferred execution and I haven’t explained it, at least conceptually, and I will do so using the context of LINQ, for simplicity.

When we have some LINQ queries, even chained ones, no matter how long, they are only executed when we explicitly tell them to do so. So, you do know at this point that LINQ is used to manipulate collections of data in a sort of a pipeline way, where a function takes a collection, does something with it, and sends it further to another function. What you don’t know is the fact that no enumeration of a query is done at all and a LINQ query is only that: a simple query, up until the point that we explicitly ask them to be executed.

To demonstrate this, I will use the code from the previous lesson, where I exemplified how we can write our own versions of a Where() and a Select() LINQ functions, but this time I will make them generic, just as they are in LINQ. Additionally, I will put two statements for displaying a message at the console, just so we can check that we actually called in those functions and they executed.


At this point, try to anticipate what the output is going to be. I am not iterating over the elements of result to display them at the console, nevertheless, I am calling both Where() and Select(), so, we should at least have the text inside them printed at the console. Let’s check:

Wrong! The output is NOTHING. There is no “Using custom Where()” or “Using custom Select()” displayed. It is as if those functions are not even called! And that is true. You can even try it for yourself, set two breakpoints, one inside the Where() function, the other inside the Select(), run the program, and your breakpoints will not be hit.

If you remember from the lesson about yield, I said there that whenever we are using yield return inside a method, the code inside that method is transformed by the compiler in an iterator block, and it is converted basically into a class, and the compiler returns an instance of that class and so on.

So, basically, we received no output because we haven’t started walking the output, both the codes inside Where() and Select() are not executed until we start consuming the elements of result.

I will do so now, but instead of using a foreach loop, I will actually use the enumerator of the enumerable. If you don’t know what that is, go read my lesson about IEnumerable and IEnumerator.

See code changes


Legend:

  • green lines with a plus near the line numbers are newly added lines
  • red lines with a minus near the line numbers are removed lines



Now, if I set a breakpoint after the LINQ query, we can observe the same effect as before, no output is provided:

Even after I ask for the enumerator, nothing happens:

But now, if I press F11 on my keyboard, or press the Step Into button in Visual Studio, the execution jumps here:

It is now inside the Select() function which is the MoveNext() for the compiler generated class of the iterator block.

So, up until I actually started asking the enumerator to give me the values of the enumerable, no query was executed, no function was called, in fact, the execution was deferred to the very last moment, when I actually needed it. After the iterator iterates all the data, we get the expected result and see that the two functions were called:

 

Tags: , , , ,

Leave a Reply



Follow the white rabbit