
Awhile back, a developer shared with me that C# 3.0 lambda expressions make
him feel "lambdumb". Over the past several months, I've met several developers
that start to look a bit nervous when they see the new "=>" operator. However,
if you've stuck with me thus far on my current series (which presents functional
programming from the vantage point of C# 2.0), there really isn't anything about
lambda expressions that should worry you. This article is minor detour from that
series to demonstrate what lambda expressions are all about.
On the surface, C# 3.0's lambda expressions are very similar in functionality
to C# 2.0's anonymous methods. Just like anonymous methods, lambda expressions
provide a way to declare code inline in a method body and allow the creation of
lexical closures when external local variables are accessed by the inline code.
(For more information on closures, see my article
here.)
The most obvious advantage of lambda expressions over anonymous methods is
their conciseness. For example, would you rather write this?
int[] numbers = { 10, 1, 9, 2, 8, 3, 7,
4, 6, 5 };
int[] lessThanFive = Array.FindAll<int>(numbers,
delegate(int
n)
{
return n < 5;
});
int sum;
Array.ForEach<int>(lessThanFive,
delegate(int
n)
{
sum += n;
});
Console.WriteLine(sum);
Or this?
int[] numbers = { 10, 1, 9, 2, 8, 3, 7, 4, 6, 5
};
int[] lessThanFive = Array.FindAll<int>(numbers,
n => n < 5);
int sum;
Array.ForEach<int>(lessThanFive, n => sum +=
n);
Console.WriteLine(sum);
Getting rid of the clunky "delegate" keywords and the curly braces certainly
does a great deal to reduce the code. However, I've spoken with some developers
that have been confused by this new syntax so let's break it down step by step.
Here's the first anonymous method:
delegate(int n) {
return n < 5; }
To convert it to a lambda expression, all we have to do is remove the
"delegate" keyword and add the funky "=>" operator like so:
(int n) => { return
n < 5; }
At this point, it should be clear that a lambda expression is still really a
method. There is a parameter list and a method body just like an anonymous
method but we've reduced the number of characters. Removing the "delegate"
keyword reduced by 8 characters but adding the "=>" operator (and a space)
gained 3 characters. So, by converting the anonymous method to a lambda
expression, we've reduced the code by a total of 5 characters. Not bad, but we
can do more.
(NOTE: Some have asked how we are supposed to pronounce the "=>" operator
when reading code. I prefer to read it as "goes to". So, if I'm reading this
lambda expression: x => x + 1, I would say "x goes to x plus one.")
Our next improvement is to allow the parameter "n" to by implicitly typed by
the compiler like this:
(n) => { return n < 5; }
that change reduces the code by 4 more characters for a total of 9. You might
be wondering if you could do the same this with an anonymous method? Well, you
can't. Parameter type inference is a feature specific to lambda expressions.
This results in a compiler error ("Identifier expected"):
delegate(n) { return
n < 5; }
Next, the current
C# 3.0 specification available from Microsoft states that "in a lambda
expression with a single, implicitly typed parameter, the parentheses may be
omitted from the parameter list." This means that, because our lambda expression
only has one implicitly typed parameter, we can remove the parentheses like
this:
n => { return n < 5; }
that gains us 2 more characters for a total of 11.
The last improvement is in the body of the lambda expression. It turns out
that lambdas support two kinds of bodies: 1) an expression or 2) a statement
block. Now, this is actually a bit confusing semantically because a single
statement that returns nothing (or, "void") can be considered an expression. So,
I prefer to think of these two body types as 1) a single statement and 2)
multiple statements. In any case, if the lambda expression contains a single
statement, it is legal to remove the curly braces and the "return" keyword (if
any). If there is more than one statement, the braces and "return" keyword are
required.
Since our lambda expression's body only consists of one statement, we can get
rid of the curly braces and the "return" keyword like this:
n => n < 5
The reduces the code by an additional 12 characters for a total of 23
characters removed. If you do the math, you'll find that we've reduced the size
of the original anonymous method by 70%! that's pretty impressive. However, for
me the biggest improvement is that, on an English keyboard, I only have to press
the shift key twice to type the lambda expression (for the '>' and '<'
characters). The anonymous method version required pressing the shift key 5
times (for '(', ')', '{', '<' and '}'). that means that this lambda is much
easier to type that its anonymous method. (This might not seem like a big deal
but remember that I'm one of the developers on the
CodeRush
product so it's very important to me.)
OK, let's take a look at the second anonymous method from our sample code:
delegate(int n) {
sum += n; }
This one doesn't reduce as much because there isn't a "return" keyword but we
can still gain quite a bit.
First, remove the "delegate" keyword and add the "=>" operator to convert it
to an anonymous method:
(int n) => { sum += n; }
Next, allow the "n" parameter to be implicitly typed:
(n) => { sum += n; }
Because this lambda expression only has one implicitly typed parameter, we
can remove the parentheses:
n => { sum += n; }
Finally, since the body of this lambda expression only contains a single
statement, we can remove the curly braces:
n => sum += n
The anonymous method version has 29 characters but the lambda expression only
has 13. that's a code reduction of about 55%.
As you can see, brevity is a huge
advantage that lambda expressions have over anonymous methods and we've really
only scratched the surface. Lambda expressions really shine when using them in
traditional functional programming techniques like
currying (the simplest
example yields a reduction of 75%).
Next time, we'll look at how lambda expressions improve the C# compiler's generic type inference.
