C# 3.0 for mere mortals, part 5: Lambda expressions
Time to continue with the longest running series on C# 3.0.
Lambda expressions originated from lambda calculus and found its way into functional languages such as Lisp and Haskel. Lambda calculus is all about calculations with functions. Functional programming on the other hand uses the application of functions to express what should be computed, not how it should be computed as in imperative programming. One of the key points of functional programming is the lazy evaluation of the functions. This gives a tremendous power, and I will revisit this topic when we talk about expressions trees.
C# developers can think of lambda expressions as the natural progression from delegates, to anonymous methods and finally to lambda expressions. Lambda expressions are about a very compact notation for “simple” functions. Let’s see that progression in action.
Take some delegate type:
public delegate string AlarmDelegate(int counter);
A normal delegate instance would need to refer to an existing and declared function inside some type.
public class FromDelegateToLambda
{
public void Demo()
{
// Conventional delegate
AlarmDelegate del = new AlarmDelegate(OnAlarm);
}
public string OnAlarm(int counter)
{
return counter.ToString();
}
}
C# 2.0 introduced the anonymous method, where you could define the method body to which a delegate object should refer, without a formal method signature declaration. The compiler would take care of the creation of a method for you. That would make the delegate instantiation like so:
// Anonymous delegate
AlarmDelegate del2 = delegate (int c) { return c.ToString(); };
A lot compacter as you can see. But, it can be even shorter. That’s where lambda expressions come in.
// Lambda expression
AlarmDelegate del3 = (int c) => c.ToString();
A lambda expression leaves out most of the syntax that is not really necessary for comprehension or compilation. The compiler can sometimes even determine the type of the input parameter c from the signature of the delegate. If that is the case, even the typing of the input parameters can be left out.
// Even shorter lambda expression
AlarmDelegate del3 = c => c.ToString();
Some other examples for lambda expressions will give you a quick insight into what is possible:
n => (n-1)*(n-2) // simple calculus
s => s.ToUpper() // invoking methods
(int x) => x++ // explicit typing
(x, y) => x ^ y // multiple parameters
(x) => { return x++; } // statement body
() => (new Random()).Next(100) // no parameters
The shorter lambda expressions usually have a expression body, meaning that there is only a single expression after the => sign. Alternatively you can have a statement body that you can instantly recognize from the curly braces. The May 2006 CTP does support both, but the latter gives incorrect syntax errors in red squigglies. Anonymous methods must have (only) a statement body, so this is one up for lambda expressions. Especially so, since lambda expressions with an expression body can be converted into an expression tree. Again, more on that when we cover expression trees.
There is some discussion as to how to pronounce the => in the expression. Depending on what the expression does it could be
- “such that”
For predicates: (Blog b) => b.Title == “Alex Thissen Weblog Build 1.15.10.1971”
- “becomes”
For projections with anonymous types: b => new { BlogName = b.Name, Title = b.Title }
- “goes to”
No idea when you would want this.
The pronounciations I came up with are depending on the return value if any:
- n => n++
Given n return n++
For functions that have a return value
- (Blog b) => b.Delete()
Given b do b.Delete()
For void return functions
Finally, as you might expect there are only that many method signatures available if you use generics. When you limit the use of ref and out parameters the set is even smaller. The only difference would be the number of arguments. The .NET Framework 3.5 will define a set of delegate types that are primarily meant for lambda expressions. Here is the list:
public delegate T Func<T>();
public delegate T Func<A0, T>(A0 arg0);
public delegate T Func<A0, A1, T>(A0 arg0, A1 arg1);
public delegate T Func<A0, A1, A2, T>(A0 arg0, A1 arg1, A2 arg2);
public delegate T Func<A0, A1, A2, A3, T>(A0 arg0, A1 arg1, A2 arg2, A3 arg3);
These delegates define functions that have 0, 1, 2, 3 and 4 parameters and return some type T. This return type is always last in the generic list. With these types you can define a delegate type of the form AlarmDelegate as Func<int, string>. There is no need to define a new delegate type.
Previous parts:
- Extensions methods
- Implicitly typed local variables and arrays
- Anonymous types
- Object and collection initializers