LINQ’d In #1 – Behavior Injection

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:

from num in Enumerable.Range(1,9)
where num % 2 == 0
select num

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).Where(n => n % 2 == 0)

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:

public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)

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:

public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
foreach (TSource t in source)
{
if (predicate.Invoke(t) == true)
yield return t;
}
}

“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:

<type>.<method>() (e.g. IEnumerable<int>.Where(…))

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:

public static class ListExtensions
{
public static IEnumerable<T> Where<T>(
this IList<T> source, Func<T, bool> predicate)
{
foreach (T t in source)
{
if (predicate.Invoke(t) == true)
{
Console.WriteLine(
String.Format(“Predicate indicated success for value: {0}”, t));
yield return t;
}
}
}
}

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:

from num in Enumerable.Range(1,9).ToList()
where num % 2 == 0
select num

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!

Example Listing
The following is a complete example suitable for pasting into snippet compiler:

using System;
using System.Collections.Generic;
using System.Linq;

public class MyClass
{
public static void RunSnippet()
{
Console.WriteLine(“Results using the IEnumerable<T>.Where() method from System.Linq.”);
var enumResults =
from n in Enumerable.Range(1,9)
where n % 2 == 0
select n;
foreach (int n in enumResults)
Console.WriteLine(n);

Console.WriteLine(“Results using the custom IList<T>.Where() method from this sample.”);
var listResults =
from n in Enumerable.Range(1,9).ToList()
where n % 2 == 0
select n;
foreach (int n in listResults)
Console.WriteLine(n);
}

#region Helper methods

public static void Main()
{
try
{
RunSnippet();
}
catch (Exception e)
{
string error = string.Format(“—\nThe following error occurred while executing the snippet:\n{0}\n—“, e.ToString());
Console.WriteLine(error);
}
finally
{
Console.Write(“Press any key to continue…”);
Console.ReadKey();
}
}

private static void WL(object text, params object[] args)
{
Console.WriteLine(text.ToString(), args);
}

private static void RL()
{
Console.ReadLine();
}

private static void Break()
{
System.Diagnostics.Debugger.Break();
}

#endregion
}

public static class ListExtensions
{
public static IEnumerable<T> Where<T>(this IList<T> source, Func<T, bool> predicate)
{
foreach (T t in source)
{
if (predicate.Invoke(t) == true)
{
Console.WriteLine(String.Format(“Predicate indicated success for value: {0}”, t));
yield return t;
}
}
}
}

This entry was posted in Technology, Uncategorized and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *