Lambda expressions tutorial for C# and Visual Studio 2008
Phil Gilmore
Lambda expressions are one of the cool new features of .NET 3.5 and are available in the C# compiler inside Visual Studio 2008. They can make code more readable and permit the framework to provide some really neat functionality when mixed with other .NET features like generics and anonymous methods.
Lambda expressions look strange at first, though, and are often hard for newcomers to understand. I saw several presentations of lambda expressions before I saw Nathan Zaugg's presentation which finally got through to me. I will attempt to describe them the way he described them in hopes that many more developers can benefit from this concept.
I will start with a description of lambda expressions and an example of how to compose one. I will then provide some examples of evolution of the C# language to demonstrate how we came to lambda expressions and thus show how you to make the transition from using the old methods to using lambda expressions.
A lambda expression is a shorthand syntax for an anonymous method.
An anonymous method is a method body that is defined in place, rather than given a name and called by name elsewhere.
Here is are some lambda expressions:
i => i * i
(i1, i2) => i1 + i2
parm => MessageBox.Show(
"Do you want to save the file: " + parm + "?",
"Confirm file save", MessageBoxButtons.YesNo)
In the above expressions, the presence of the => operator makes it a lambda expression. On the left side of the expression is a parameter list. On the right side is an expression to be evaluated. The expression may contain the parameters that were defined on the other side of the => operator.
In essence, lambda expressions define a function's parameters and its body. It has the limitation that the body must be a single expression which returns a single object. They can be used anywhere a delegate would be used. They are callback methods.
Here is a demonstration of the evolution of C# from callbacks to lambda expressions. In these demonstrations, I will define the following code, which I will reuse in each example:
public delegate int IntCallback(int parameter1, int parameter2);
protected int AddIntegers(int value1, int value2)
{
return value1 + value2;
}
First, I declare a delegate which takes two integer parameters and returns an integer. Then I define a method that matches this delegate signature, AddIntegers(). I go on to define a method that uses such a callback. Any code that calls this method must pass a callback of the IntCallback delegate type.
protected int CalculateSum(IEnumerable<int> values, IntCallback callback)
{
int result = 0;
foreach (int value in values)
result callback(result, value);
return result;
}
Finally, I declare a small integer sequence for use in the demonstrations.
List<int> intData = new List<int>(
new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
I will start with a traditional callback. This passes a delegate instance of AddIntegers as the callback to CalculateSum. CalculateSum will repeatedly call AddIntegers and return the sum of all numbers in the intData sequence.
IntCallback callback = new IntCallback(AddIntegers);
int total = CalculateSum(intData, callback);
In recent versions of Visual Studio, you do not have to invoke the constructor of the delegate type to create an instance of it when passing as a method parameter. The compiler is smart enough to instantiate it based on the prototype of the parameter in question.
int total = CalculateSum(intData, AddIntegers);
In Visual Studio 2005, Anonymous methods were introduced, allowing you to declare the callback method body in place. Using anonymous methods, the AddIntegers method is no longer used. Instead, its body is declared in place.
int total = CalculateSum(intData,
delegate(int value1, int value2)
{
return value1 + value2;
}
);
Using the delegate keyword here tells the compiler that a parameter list and method body follow. The parameter list and return type of this method must match the signature of the delegate for this parameter (IntCallback), or you will get a compiler error. Notice that this syntax is identical to a normal method except for the return type and the method name are missing.
The next evolution was a lambda expression, which is a shorthand notation for the anonymous delegate shown above. Let's analyze the anonymous method and convert it to a lambda expression.
delegate(int value1, int value2)
{
return value1 + value2;
}
In the above code, the parameter list is:
(int value1, int value2)
This becomes the left side of our lambda expression.
(value1, value2) =>
The types are usually omitted. This is because as the lambda expression is passed as a parameter whose type is a delegate, the parameter is already defined as a method with certain parameters. In this case, the parameter is an IntCallback, and IntCallback defines a parameter list of (int value1, int value2). Given that, the compiler uses type inference to determine the types of the parameters in the lambda expression. The return type is dermined in the same way.
Next we extract the method body. This is the right-hand side of the lambda expression. This is sometimes called the predicate. This should not be confused with any parameter whose name is predicate. In such instances, a complete lambda expression is expected.
Here is the method body, extracted from the anonymous method.
return value1 + value2;
Given that, we can complete our lambda expression. Remember that the predicate is a single expression rather than a multitude of instructions. Hence, we do not use the return keyword.
(value1, value2) => value1 + value2
When this method is passed to CalculateSum, CalculateSum will call the the method which was passed in as parameter callback. This passes the value1 and value2 parameters to our lambda expression, which plugs them in and evaluates the expression value1 + value2 and returns the result to the caller (CalculateSum).
int total = CalculateSum(intData,
(value1, value2) => value1 + value2
);
Phil Gilmore (www.interactiveasp.net)