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 ;-)