Wednesday, October 24, 2007

C# 3.0 Features: Lambda Expressions

C# 3.0 Lambda expressions come very handy to developers. One important consideration (which is beautiful) is that all these features (LINQ, C# 3.0 features, Entity Framework etc) come with no change in CLR. That means your old CLR, which executes your .Net 2.0 apps can execute your new apps too. And that implies that the IL that is emitted hasn't changed. So, though you use Lambda expressions instead of anonymous methods, the IL generated is the same in both cases. (Wow!!! Now I can feel the beauty of the IL concept and design.)

Coming back to Lambda expressions, what are they basically? They are not just a syntactic substitute to anonymous methods. You can read more about Lambda expressions (and an interesting topic of how to pronounce them!!) in Eric White's blog. I will deal with two important things of Lambda expressions (in context of LINQ) which you may not find often on the web.
  1. Writing whole blocks of code in Lambda expressions. (Remember that Lambdas are expressions, hence writing more than one statement may sound little odd)
  2. Returning subtypes (or columns) of a type on which the LINQ query is fired.

Writing whole blocks
Lambda expressions definitely make writing anonymous methods easy. Using Lambda you can do all that you can with anonymous and even more. So, if you can write a proper function with different control statements and logic using anonymous, you can do the same with lambda. Most of the lambda examples you see would be something like this:

var existingAccount = repository.Accout.Where(a => a.AccountId == "1");

You can write quite an amount of logic here, something like this:

var existingAccount = repository.Accout.Where(a =>
if (a.AccountId == "1" && a.AccountName == "Pavan")
return true;
return false;

The above code snippet will return an Account object after processing all the logic, accordingly. So, as you can see, it's just like any other (anonymous) function.

Now, if you carefully observe the snippet above, we are returning a boolean value from within the lambda but what existingAccount contains is an Account object. How did this happen? Well, it's pretty simple. If you carefully inspect signature of "Where" method being called on Account, you can understand.

What Where expects is a Linq.Expressions.Expression parameter which, in turn, expects a delegate that takes an input parameter as InsuranceDBWrapper.Account and returns a boolean. And this delegate's return value decides whether to return the object (in our case Account) on which Where is being called. And that's exactly what our lambda does - to return a boolean telling whether to return the (Account) object or not. Note that you can pass the delegate directly also without any expression in between.

Returning Subtypes (or columns)
What if you don't want the whole type (or object) to be returned but just some columns or subtypes of it? You can as well do that, but not using Where. Where has 4 overloads of which two take expressions and the other two, directly delegates. But the sig for these delegates involves atmost two input parameters (first parameter is the type on which Where is applied and which is inferred automatically, second an int which gives the index of an item) and always returns a boolean (which tells whether to return the whole object or nothing). So, there is no means here to select and return specific columns. Since you return a boolean, it's either the complete object or nothing - that is returned.

What you need to use in this case is a Select:

var existingAccount = repository.Accout.Where(a => a.AccountId == "1").Select(b => new {b.AccountId, b.Name, b.IsLocked});

This returns an anonymous type with three properties (AccountId, Name, IsLocked).

Select is used to filter on columns and Where is used to filter on rows.

Lambdas can be used in Expression trees (which will be in a separate post :))

No comments: