Page Options
You are here : News
MADdotNET News Minimize
Feb 24

Written by: Aaron Hoffman
2/24/2009 8:36 PM 

Deconstructing a LINQ Statement

by Aaron Hoffman

(download source code: http://aaronhoffman.googlecode.com/files/AaronHoffmanLinqDemov01.zip )

 

LINQ is a valuable tool, but because of the way it is usually demonstrated, developers think that its only use is for querying a Microsoft SQL Server database. This is because what is usually being demonstrated along with LINQ, is LINQ to SQL. LINQ is not just LINQ to SQL. I like to think of LINQ as a shorthand (or syntax shortcut) way of writing code - a way to write less lines of code (or simplify code), but still perform the same operations (kind of like what foreach is to the for loop).

I would like to demonstrate LINQ in a way that does not use LINQ to SQL. I will break down a LINQ statement into lines of code that developers might be more familiar with in an attempt to show what is going on under the covers. I will start with code that hopefully everyone has seen/written before, then build up to a LINQ statement that performs the same operation. This way you will see what the compiler turns your LINQ Statements into.

In each example we will search through a collection of Customer objects to find all the Customers that meet our criteria (that "pass our test"). The following statement will be used in each example to filter the collection (I will refer to this as: The Test)

 

c.CompanyName.ToUpper().StartsWith(OUR_STRING.ToUpper());

 

 

Example 1
In this example we will use a foreach loop to iterate through a collection of Customers. If a Customer passes our test, we will add it to a second collection of Customers... That’s it.

 

  108 public List<Customer> GetCustomersByCompanyName1(string CompanyName)

  109 {

  110    //Create a collection of customers to temporarily hold any Customers that

  111    //Pass our test

  112    List<Customer> FoundCustomers = new List<Customer>();

  113 

  114    //Iterate through the collection of Customers and add any Customer that

  115    //passes our test to the FoundCustomers collection.

  116 

  117    //Side Note: This foreach loop is very similar to the

  118    //List<T>.FindAll(Predicate<T>) method which will be used later in this demo

  119    foreach (Customer c in MyCustomers)

  120    {

  121       //The Test

  122       if (c.CompanyName.ToUpper().StartsWith(CompanyName.ToUpper()))

  123          FoundCustomers.Add(c);

  124    }

  125 

  126 

  127    return FoundCustomers;

  128 }

 

 

Example 2
In this example we will use the foreach loop again, but we will move The Test out of the loop and into its own class. This can be a little confusing – it seems like an extra/unnecessary step. This is just to help show you what the compiler does when you write an anonymous method (which we haven’t done yet, but we’re going to...). If this example confuses you, just read on, then come back to it later.

 

  142 public List<Customer> GetCustomersByCompanyName2(string CompanyName)

  143 {

  144    //Create a collection of customers to temporarily hold any Customers that

  145    //Pass our test

  146    List<Customer> FoundCustomers = new List<Customer>();

  147 

  148    //Iterate through the collection of Customers and add any Customer that

  149    //passes our test to the FoundCustomers collection.  Instead of writing

  150    //The Test statement in-line, here we have moved it out into its own

  151    //class/method.  This is to help demonstrate anonymous methods, which we

  152    //will be going over later.

  153 

  154    //initialize the class by setting the CompanyName property

  155    TheClassThatHasTheTest tt = new TheClassThatHasTheTest();

  156    tt.CompanyName = CompanyName;

  157 

  158    //Side Note: This foreach loop is very similar to the

  159    //List<T>.FindAll(Predicate<T>) method which will be used later in this demo

  160    foreach (Customer c in MyCustomers)

  161    {

  162       if (tt.CompanyNameStartsWith(c))

  163          FoundCustomers.Add(c);

  164    }

  165 

  166 

  167    return FoundCustomers;

  168 }

 

 

Example 3
In this example the foreach loop from the previous examples has been replaced by the FindAll() method. The FindAll() method does exactly what we have been doing manually in the past two examples. It will loop through the collection of Customers and test each Customer to see if it passes our test. If it does, it will add that Customer to a second collection and eventually return that collection. All we have to do is pass in The Test.

 

Now, if you have not worked with delegates before, this is where it might get a little fuzzy. Although, you probably have worked with delegates before without knowing it. If you have ever handled a Button’s Click event, you have worked with delegates.

You can think of a Delegate like a method that can be passed around like a variable (kind of like a method pointer). And Delegates also have types. The type of a delegate defines what the method signature needs to look like, but not the logic within the method. A Button’s Click Event Delegate has a type of EventHandler. The definition of which looks like this:

 

public delegate void EventHandler(object sender, EventArgs e);

 

And so, your Button_Click method needs to look like this:

 

public void Button1_Click(object sender, EventArgs e) { }

 

Your button_click method needs to return void and have two parameters, an object and an EventArgs class. The name of the method and the logic within it do not matter.

 

Now back to the FindAll() method. The FindAll() method has a single parameter that is a Delegate of type Predicate<T>. Here is the definition of the Predicate<T> Delegate:

public delegate bool Predicate<T>(T obj);

So, in order to match this delegate type, we need to write a method that returns a Boolean and takes one parameter that is the same type as the Generic List (that is what "<T>" means – in our case that would be a Customer). We can then pass that method in to the FindAll() method so it can use it to test each Customer. Well, we have already written that method, haven’t we? It is the same method that we called within the foreach loop in Example 2. It is The Test:

Take a look at the code for Example 3. The foreach loop has been replaced by the FindAll() method

 

  183 public List<Customer> GetCustomersByCompanyName3(string CompanyName)

  184 {

  185    //initialize the class by setting the CompanyName property

  186    TheClassThatHasTheTest tt = new TheClassThatHasTheTest();

  187    tt.CompanyName = CompanyName;

  188 

  189    return MyCustomers.FindAll(tt.CompanyNameStartsWith);

  190 }

 

 

Example 4
If this isn’t making much sense to you, just stick with it, you don’t need to know all of the inner workings of LINQ in order to use LINQ.

In this example, instead of using the “CompanyNameStartsWith” method we used previously, we just write that logic in-line again. This time, however, we use an anonymous method. Within the FindAll() method call, we write a new method in-line using the delegate() keyword. The benefit of this is that we write less lines of code. And the reason we don’t have to write the code for the class that would hold this one method, is that the compiler does it for us! Click Here if you want more information on how this is done.

 

  205 public List<Customer> GetCustomersByCompanyName4(string CompanyName)

  206 {

  207    //Here we write The Test in-line again - the compiler will create a new

  208    //class to hold this method for us at compile time

  209    return MyCustomers.FindAll(delegate(Customer c)

  210    {

  211       return c.CompanyName.ToUpper().StartsWith(CompanyName.ToUpper());

  212    });

  213 }

 

 

Example 5
Another feature that released along with LINQ are Lambda Expressions. And like LINQ, I like to think of Lambdas as a syntax shortcut or Shorthand way of writing code. It is a way to write/create delegates in less lines of code. The “delegate” keyword and brackets have been replaced by the new Lambda operator (=>). And we can now re-write the example above in one clean line of code. A little too clean, you might say. How does it know what ‘c’ is? Click Here (and Here) for more information.

 

  227 public List<Customer> GetCustomersByCompanyName5(string CompanyName)

  228 {

  229    //Same idea as example 4 - except now we are using Lambdas.

  230    return MyCustomers.FindAll(

  231       c => c.CompanyName.ToUpper().StartsWith(CompanyName.ToUpper()));

  232 }

 

 

Example 6
The first (and only) example with LINQ! This example does basically what the past examples have done, with a few differences. You probably noticed the ‘var’ keyword. Var is an example of an implicitly typed local variable. You don’t need to declare the type that will be returned by the LINQ statement (because sometimes it won’t exist yet...), but the compiler will determine the type for you. In this example, we could replace var with IEnumerable<Customer>, but it is a LINQ convention to just use var. (I will not be explaining LINQ syntax here, there are plenty of resources that do that already. I just want to demonstrate what the compiler does to your LINQ statements). Take a look at the code.

 

  252 public IEnumerable<Customer> GetCustomersByCompanyName6(string CompanyName)

  253 {

  254    var query =

  255       from c in MyCustomers

  256       where c.CompanyName.ToUpper().StartsWith(CompanyName.ToUpper())

  257       select c;

  258 

  259 

  260    return query;

  261 }

 

 

Example 7
The LINQ query above can also be written using the IEnumerable<T>.Where(Func<T, bool>) extension method. The Where() method is a lot like the FindAll() method, but way better. (I won’t get into why it is better – that is another post). You might then ask, Why would you ever write this as a LINQ statement then? Why don’t we just use the Where() method from the start? This is one of the beauties of LINQ. The Where() method isn’t the only method a LINQ statement is turned into (although, it is probably a popular one). But, when you are writing a LINQ statement, you don’t have to worry about which methods need to get called, you just have to write in one standard syntax, and all the work is done for you by the compiler! Not only does it determine the type that will be returned by the statement, it also determines the methods that need to get called! (This process, of course, is a little more complicated than that – but that is the short and sweet answer). Take a look at the code.

 

  283 public IEnumerable<Customer> GetCustomersByCompanyName7(string CompanyName)

  284 {

  285 

  286    var query = MyCustomers.Where(c => c.CompanyName.ToUpper().StartsWith(CompanyName.ToUpper()));

  287 

  288 

  289    return query;

  290 }

 

 

Example 8
There is nothing new in this example. I just wanted to come full circle and show you that the compiler will turn “the meat” of the LINQ statement into a bunch of little methods for you. Methods and Classes that you never had to write!

 

  302 public IEnumerable<Customer> GetCustomersByCompanyName8(string CompanyName)

  303 {

  304    //initialize the class by setting the CompanyName property

  305    TheClassThatHasTheTest mc = new TheClassThatHasTheTest();

  306    mc.CompanyName = CompanyName;

  307 

  308    var query = MyCustomers.Where(mc.CompanyNameStartsWith);

  309 

  310 

  311    return query;

  312 }

 

 

Summary
And that’s it... I hope you were able to learn something from this demonstration. If nothing else, just start using LINQ! Like I said previously, LINQ is not just LINQ to SQL. And you don’t have to know all the inner workings of the compiler to start using it. So Get Started!

 

-Aaron Hoffman

 

--

Here are the resources I used to gather information for this post.

Scott Gu's LINQ to SQL Guide:
http://weblogs.asp.net/scottgu/archive/2007/09/07/linq-to-sql-part-9-using-a-custom-linq-expression-with-the-lt-asp-linqdatasource-gt-control.aspx

LINQ: .NET Language-Integrated Query
http://msdn.microsoft.com/en-us/library/bb308959.aspx

LINQ to SQL: .NET Language-Integrated Query for Relational Data
http://msdn.microsoft.com/en-us/library/bb425822.aspx

More information about anonymous methods & delegates and how they relate to the compiler
http://msdn.microsoft.com/en-us/magazine/cc163682.aspx

Delegates Overview
http://msdn.microsoft.com/en-us/library/ms173171.aspx

Lambdas
http://blogs.msdn.com/charlie/archive/2008/06/28/lambdas.aspx

'var' c# reference
http://msdn.microsoft.com/en-us/library/bb383973.aspx

--

Tags:
Categories Minimize
Search Blogs Minimize
Publish Dates Minimize
Print  

Copyright 2009 by Lance Larsen
Privacy StatementTerms Of Use