There are three key things that you need to understand in order to understand Lambda expressions in C#3.0.
1. Delegates,2. Delegates,3. ... delegates.
Since Lambda has come to town (or C#) I've discovered that a lot of people use delegates, but quite a few developers don't really know what a delegate is. Understanding delegates is key to understanding what Lambdas are.
Wikipedia has the following definition:A delegate is a form of type-safe function pointer used by the .NET Framework. Delegates specify a method to call and optionally an object to call the method on. They are used, among other things, to implement callbacks and event listeners.
Here is my rule of thumb definition:A delegate definition is a signature description for a method.
It helps me to think of it as an interface definition for methods. An interface defines a number of methods, properties and/or events that need to be implemented by a class. You can use an interface definition as a type for a parameter in a method. Similarly a delegate definition defines the return type and parameters that a method needs to 'implement' (match is a better word). And a delegate can also be used as a type for a parameter in a method.
Still a lot of mumbo jumbo?Okay, let’s take the journey from ‘traditional’ programming to C#3.0 syntax.
Consider the following program. Traditionally if you wanted to select a subset from a collection you would iterate over the collection and copy the items you're interested in into a new collection.
static void Main(string[] args){ List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; List<int> evenNumbers = GetEvenNumbers( numbers ); foreach ( int i in evenNumbers ) { Console.WriteLine( i ); } Console.ReadLine();}
static List<int> GetEvenNumbers( List<int> numbers ){ List<int> result = new List<int>(); foreach ( int i in numbers ) { if ( i % 2 == 0 ) { // if i matches the filter then add it to the result result.Add( i ); } } return result;}
The above code will select all the even numbers and print them to the console. Just a foreach and no delegates yet.
Instead of implementing a 'GetEvenNumbers' method, lets create a generic Select method. The method will be generic and will loop over the numbers and select only those which are selected by a filter. But how to define the filter? Lets first look at what we want our Select method to look like:
static List<int> Select( List<int> numbers, Filter filter ){ List<int> result = new List<int>(); foreach ( int i in numbers ) { // call the filter method/delegate to see if this // number needs to be in the result if ( filter( i ) == true ) { // if i matches the filter then add it to the result result.Add( i ); } } return result;}
We want to create a method which takes a reference to another method as a parameter. This is what is known as a delegate. In our case the delegate needs to be a method which takes an Int32 as a parameter and returns a Boolean. In C# the delegate is defined using the 'delegate' keyword:
delegate bool Filter( int x );
Now, any method that takes an Integer as a parameter and returns a Boolean matches this delegate. So both methods shown below match the delegate:
static public bool IsEvenNumber( int number ){ return ( number % 2 == 0 );}
private bool IsOddNumber( int z ){ return ( z % 2 == 0 );}
Notice that the name of the method does not matter, nor does the name of the parameter, nor does the accessibility of the method. Types of the parameter(s) and the return type is all that matters when it comes to matching with the delegate.
We can now rewrite Main to use the Select method:
static void Main( string[] args ){ List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; List<int> evenNumbers = Select( numbers, IsEvenNumber );
foreach ( int i in evenNumbers ) { Console.WriteLine( i ); } Console.ReadLine();}
In C# 3.0 it has become easier to create a delegate, all you need to do is provide the name of the method, the compiler infers that you wish to create a delegate.
The old syntax was:
static void Main( string[] args ){ List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; List<int> evenNumbers = Select( numbers, new Filter( IsEvenNumber ) ); foreach ( int i in evenNumbers ) { Console.WriteLine( i ); } Console.ReadLine();}
The syntax is shorter, and arguably more readable, but hides the fact that a delegate is in fact an object and not just a reference as the new syntax seems to suggest.
Ok, now our IsEvenNumber method is quite simple, right? So to create a whole method for that method, which will likely never be reused is a little much. This is where anonymous delegates come in. Rather than specify a method which matches the delegate we can create an anonymous delegate which also matches with the Filter delegate.
Consider the following code:
static void Main( string[] args ){ List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; List<int> evenNumbers = Select( numbers, delegate( int x ) { return ( x % 2 == 0 ); } ); foreach ( int i in evenNumbers ) { Console.WriteLine( i ); } Console.ReadLine();}
The reference to IsEvenNumber has now been replaced by the creation of an anonymous delegate. The creation and implementation of the delegate is all done in a single line.
You'll probably agree that this does not do much for readability.
Remember how I started this blog post? Lambdas are delegates. So let’s replace the delegate with a lambda.
static void Main( string[] args ){ List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; List<int> evenNumbers = Select( numbers, ( x => ( x % 2 == 0 ) ) ); foreach ( int i in evenNumbers ) { Console.WriteLine( i ); } Console.ReadLine();}
So what is happening here? Let’s look at the Lambda:
x => ( x % 2 == 0 )
We are expressing that there is an input 'x' which we want to mod by two. Since this Lambda expression is only a single line the return statement is implied and automatically applied to the last statement, most Lambda expressions contain only one line of code, but any number is possible. If you want more that one line you need to use the { } to denote the scope of the block and use the return statement (if your delegate return anything other than void). If we were to change the expression to:
x => ( x % 2 )
Then the result of the Lambda would be a integer and the code would not compile since the return type Int32 does not match with the Filter delegate.There is quite a bit of 'magic' going on for the compiler to decide which delegate the Lambda maps onto, the intellisense will help you, because (in this sample) it will know that 'x' is of type Int32.
So to summarize:Lambdas are a concise notation for defining delegates.
dasBlog theme by Mads Kristensen
Concepts LINQ Entity Framework WCF WPF RESTful Web Unit Testing .NET Workflow More >>
Tools Visual Studio Windows IIS Silverlight More >>
Type Screencast Tools Video Newsletter Sample Article Books Magazine How To Demo Course Products More >>