Partager via


Lambda Expressions in C#

Before being able to talk about Lambda expressions, I need to wet your appetite by touching on the difference between Expressions and Statements:

i = j;

while (a) f(b);

The above listing is an illustration of 2 statements. As you can see evaluation order is important when it comes to statements.

2 * a + 10 * b

(a - 12) + (b * 2 + 10)

In the above listing, we have 2 expressions. In the second expression we can evaluate either parenthesis first or even both together and that has no effect on the result of the expression. So, evaluation order is unimportant in expressions provided there are no side effects.

You can introduce a side effect in the above expression by for instance overloading the * operator and within its execution change the value of a.

Expressions can be shorter and more concise than their corresponding statements. The example below compares an expression with a number of statements that recreate the effect of the expression:

int r = (2 * a * b + c) * (10 * b * d);

with no expressions:

int r = 2; r *= a; r *= b; r += c;

r *= 10; r *= b; r *= d;

In order to be able to discard statements, expression language must be extended. That is what most functional programming languages have been aiming to achieve. Unfortunately most have miscalculated the need for statements and the programmers’ love for them.

So what are Lambda expressions? And why are they hot again?

Lambda expressions are derived from Church’s λ-calculus theory of compatibility (1941). A lambda expression is a short hand notation for anonymous function definitions:

Integer -> Integer
λ x -> x * 2

The first line of the above listing defines the type of the lambda expression (takes a parameter of type Integer and returns an Integer). The second line is a lambda expression that defines an anonymous function similar to the one below in C# 2.0 (takes a parameter called x and the body of the function return x * 2):

delegate(int x)

{

   return x * 2;

}

Here are some more lambda expressions:

λ (x, y) -> x * 10 + y
λ (x, y, z) -> (1/x + 10) * y + z

As you can see, the notation for lambda expressions does not explicitly define the type of parameters and the return value. Therefore in most functional programming languages the type of the function must be specified separately.

Lambda expressions are hot again because of their introduction in C# 3.0 which is due to be released as part of .NET Framework 3.5.

In C# 3.0, according to MSDN books online, a lambda expression is defined as:

“An inline expression or statement block with a concise syntax that can be used wherever a delegate type is expected. For example, you can use a lambda expression as an argument to a method call, to assign a value to a delegate or to define an event handler. Lambdas can be used virtually anywhere that anonymous methods can be used.”

C# 3.0 has a lambda operator (=>) which is functionally identical to the arrow or the dot in λ-calculus. The lambda operator has the same precedence as assignment (=) and is right-associative. In C# 3.0, here are the equivalent lambda-expressions to the ones demonstrated above:

x => x * 2;

(x, y) => x * 10 + y;

(x, y, z) => (1 / x + 10) * y + z;

The above lambda expressions return an instance of a delegate. However the type of the delegate needs to be defined. In System.Linq namespace, there are a number of generic delegates which could be used in order to compose new delegate types for your lambda expressions. These generic delegates are:

public delegate TResult Func<TResult>();
public delegate TResult Func<TArg0, TResult>(TArg0 arg0);
public delegate TResult Func<TArg0, TArg1, TResult>(TArg0 arg0, TArg1 arg1);
public delegate TResult Func<TArg0, TArg1, TArg2, TResult>(TArg0 arg0, TArg1 arg1, TArg2 arg2);
public delegate TResult Func<TArg0, TArg1, TArg2, TArg3, TResult>(TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3);

Now we can complete our lambda expressions:

Func<int, int> f1;

f1 = x => x * 2;

Func<int, int, int> f2;

f2 = (x, y) => x * 10 + y;

Func<int, int, int, int> f3;

f3 = (x, y, z) => (1 / x + 10) * y + z;

// A lambda expression with no arguments

Func<int> f4 = ()=> 10;

It is possible to use f1, f2 and f3 like any other delegate instances:

int r1 = f1(10);

int r2 = f2(5, 10);

int r3 = f3(13, 2, 3);

A lambda expression is transformed into an instance of a delegate pointing at a method. The method is auto generated at compile time with its body being the same as the body of the original lambda expression. For example:

Func<int, int> f1 = x => x * 2;

is transformed to the following:

Func<int, int> f1 = delegate(int x)

{

   return x * 2;

};

The following points are taken directly from C# 3.0 Specification:

- Lambda expressions permit parameter types to be omitted and inferred whereas anonymous methods require parameter types to be explicitly stated.

- The body of a lambda expression can be an expression or a statement block whereas the body of an anonymous method can only be a statement block.

- Lambda expressions with an expression body can be converted to expression trees

- The parameters of a lambda expression can be explicitly typed:

(int x) => x * 2;

One last topic to introduce here is the notion of closure: Local variables can be used inside the body of lambda expressions. The behaviour here is very similar to how closure works in anonymous methods:

double y = 0;

Func<double, double> t = x => x * y;

double r1 = t(10);

y = 10;

double r2 = t(10);

After executing the above code, r1 is 0 and r2 is 100.

Comments

  • Anonymous
    June 02, 2007
    Can't wait to start playing around with these!  I've got Orcas Beta 1 downloaded but havn't had the time to install it or play around with C# 3.0. Are you going to post about Expression Trees?  Those seem very interesting to me as well.

  • Anonymous
    April 27, 2010
    Excellent ! very good article for lameda expressin. thanks!