Decomposing LINQ

Sun, February 18, 2007, 07:04 PM under dotNET | Orcas | LINQ
Please revisit the example from my previous post on LINQ. Here I only include the query snippet of code:
       var results =
from p in Process.GetProcesses()
where p.Threads.Count > 6
select new {p.ProcessName, ThreadCount = p.Threads.Count, p.Id };
...or in VB if you prefer:
    Dim results = _
From p In Process.GetProcesses() _
Where p.Threads.Count > 6 _
Select New With {p.ProcessName, .ThreadCount = p.Threads.Count, p.Id}
The above is based on a number of new language features including one that I have not mentioned until now: query expressions. If we were to take away query expressions, the code above would look like this:
   var results =
Process.GetProcesses()
.Where(p => p.Threads.Count > 6)
.Select(p=>new {p.ProcessName, ThreadCount = p.Threads.Count, p.Id });
In VB:
    Dim results = _
Process.GetProcesses() _
.Where(Function(p) p.Threads.Count > 6) _
.Select(Function(p) New With {p.ProcessName, .ThreadCount = p.Threads.Count, p.Id})
The two snippets above are identical. There isn't much more to explain about Query Expressions other than... that is the way it is! The two code snippets above are identical, the first one using query expression syntax to beautify the real code shown in the 2nd one.

So let's look at the 2nd snippet more closely. The first line (Process.GetProcesses()) returns an array and we know that arrays implement IEnumerable. Well it turns out that in Framework 3.5 there are some extension methods for IEnumerable including a method called Where that takes as an argument a delegate (named Func) and returns an IEnumerable. This extension method (along with other extension methods) and the delegate it accepts (along with other delegates) are in the System.Linq.Enumerable class that is in Core dll.

Given what we just said, the second line above (.Where(p => p.Threads.Count > 6)) should make a lot more sense now: the way we pass in a delegate to the Where extension method is by using a lambda expression (VB lambda link). The 3rd line of code is also straightforward once you know that another extension method on IEnumerable is Select. It also takes a delegate and we used a lambda expression for that as well. Note how in the body of the lambda we use anonymous types. What Select returns is a generic IEnumerable of the anonymous type we create on the fly. Since we cannot write compilable code of an IEnumerable of an anonymous type, we use local variable type inference (VB inference link) for the results variable and again take advantage of inference when we want to access each element:
      foreach (var o in results)
{
Console.WriteLine(o.ToString());
}
Note that if you look closely at the extension methods in Enumerable and the delegates in the System.Linq namespace, you will find extreme usage of generics and that is how that code can deal with arbitrary conditions, anonymous types etc that are used in LINQ queries. Don’t forget the compiler magic of course that does generate quite a bit of goo in addition to the code you write. For that last point, compile the code above and open the assembly with reflector and you'll see what I mean :-)

To finish off, take a glance at the original query without the query expression syntax; what would the code look like if we also took away all the new language features (but still using the new Enumerable class of the Framework 3.5)? First we have to write two extra methods and declare a helper class):
    private class MothAnonymousType
{
public string ProcessName;
public int ThreadCount;
public int Id;
//
public override string ToString()
{
return "{ ProcessName = " + ProcessName + ", ThreadCount = " + ThreadCount + ", Id = " + Id + " }";
}
}
private static bool MothWhere(Process p)
{
return p.Threads.Count > 6;
}
private static MothAnonymousType MothSelect(Process p)
{
MothAnonymousType type1 = new MothAnonymousType();
type1.ProcessName = p.ProcessName;
type1.ThreadCount = p.Threads.Count;
type1.Id = p.Id;
return type1;
}
and then our calling code changes to this:
    static void Main(string[] args)
{
IEnumerable<MothAnonymousType> results =
Enumerable.Select(
Enumerable.Where(Process.GetProcesses(), new Func<Process, bool>(MothWhere)),
new Func<Process, MothAnonymousType>(MothSelect));
//
foreach (MothAnonymousType o in results)
{
Console.WriteLine(o.ToString());
}

Console.ReadLine();
}


I think I prefer the original query syntax don’t you ;-)