As part of a continuing set of articles on LINQ, I want to first take a moment to look at how the “natural” syntax of LINQ is turned into executable code. Once we understand how those expressions and compiled we can investigate ways to extend the code that is being executed.
First of all let’s consider a very simple LINQ expression:
This expression selects the even numbers in the series 1–9. Let’s first break this down into the actual code that is being executed. Behind the scenes LINQ is viewing this expression as a series of lambda expressions and extension methods. Our example is actually being run as:
Enumerable.Range(1,9) yields an IEnumerable<int>. By using Reflector we can discover that the signature of the Where method looks like the following:
This is clearly an extension method on IEnumerable<TSource> that takes a Func<TSource, bool> as its only parameter. While the actual internals of the Where method are actually a little more complex due to the way in which iterators have been abstracted, we could theorize that the behavior is roughly similar to the following:
“For each item in the supplied IEnumerable, invoke the supplied predicate and add the item to the returned collection if that predicate returned true.”
Note: If you haven’t encountered keyword yield before, check out this post about how it works.
Now that we understand how the statement is working internally, lets look at ways in which we can extend it. To understand the following trick we need to think for a second about how extension methods are implemented, or more importantly about how they are resolved.
When the compiler encounters a statement of the form:
for which there is no explicitly defined method, it searches the current list of scoped namespaces for an extension method that matches the implied signature. The System.Linq namespace defines the extension method we’ve been examining above. However, what if we wanted to use our own method instead of the System.Linq version of Where()?
We might want to add in some logging statements when the predicate finds a match. Let’s do this right now by adding our own extension method for the IList<T> generic. To do this we need to define a static class that contains a static Where method that has this IList<T> as its first parameter:
This method is almost identical to the theoretical version of Where we built above. The only differences are that it extends IList<T> instead of IEnumerable<T> and that it logs when the predicate finds a match prior to yielding the matched item.
Now let’s put together a little snippet that will exercise this extension method. We can actually use the first sample we used in this post and by simply adding ToList() to the end of Enumerable.Range(1,9) we’ll be calling our extension method for Where() instead of the built-in System.Linq version. Let’s look at that sample:
By simply adding the ToList() to the sample above we’ve caused the compiler to call our new Where() method. This gives a pretty clear idea of how LINQ is working internally and what’s actually going on when we use the new C# 3.0 language keywords. There is a lot less voodoo involved in the process that one might first believe!
The following is a complete example suitable for pasting into snippet compiler: