C# 3.0 Lambda Expressions

A lambda expression is an unnamed method written in place of a delegate instance. The compiler immediately converts the lambda expression to either:

• A delegate instance.

• An expression tree, of type Expression<TDelegate>, representing the code inside the lambda expression in a traversable object model. This allows the lambda expression to be interpreted later at runtime.

Given the following delegate type:

delegate int Transformer (int i);

we could assign and invoke the lambda expression x => x * x as follows:

Transformer sqr = x => x * x;
Console.WriteLine (sqr(3)); // 9

A lambda expression has the following form:

(parameters) => expression-or-statement-block

For convenience, you can omit the parentheses if and only if there is exactly one parameter of an inferable type.
In our example, there is a single parameter, x, and the expression is x*x:

x => x * x;


Each parameter of the lambda expression corresponds to a delegate parameter, and the type of the expression (which may be void) corresponds to the return type of the delegate.
In our example, x corresponds to parameter i, and the expression x * x corresponds to the return type int, therefore being compatible with the Transformer delegate:

delegate int Transformer (int i);

A lambda expression's code can be a statement block instead of an expression. We can rewrite our example as follows:

x => {return x * x;};


Explicitly Specifying Lambda Parameter Types
The compiler can usually infer the type of lambda parameters contextually. When this is not the case, you must explicitly specify the type of each parameter. Consider the following delegate type:

delegate int Transformer (int i);

The compiler uses type inference to infer that x is an int by examining Transfomer's parameter type:

Transformer d = x => x * x;

We could explicitly specify x's type as follows:

Transformer d = (int x) => x * x;



Generic Lambda Expressions and the Func Delegates
With generic delegates, it becomes possible to write a small set of delegate types that are so general they can work for methods of any return type and any (reasonable) number of arguments.

These delegates are the Func and Action delegates, defined in the System namespace.
Here are the Func delegates (notice that TResult is always the last type parameter):

delegate TResult Func <T> ();

delegate TResult Func <T, TResult>
(T arg1);

delegate TResult Func <T1, T2, TResult>
(T1 arg1, T2 arg2);

delegate TResult Func <T1, T2, T3, TResult>
(T1 arg1, T2 arg2, T3 arg3);

delegate TResult Func <T1, T2, T3, T4, TResult>
(T1 arg1, T2 arg2, T3 arg3, T4 arg4);

Here are the Action delegates:

delegate void Action();

delegate void Action <T>
(T arg1);

delegate void Action <T1, T2>
(T1 arg1, T2 arg2);

delegate void Action <T1, T2, T3>
(T1 arg1, T2 arg2, T3 arg3);

delegate void Action <T1, T2, T3, T4>
(T1 arg1, T2 arg2, T3 arg3,
T4 arg4);

These delegates are extremely general. The Transformer delegate in our previous example can be replaced with a Func delegate that takes a single int argument and returns an int value:

Func<int,int> sqr = x => x * x;
Console.WriteLine (sqr(3)); // 9




Outer Variables
A lambda expression can reference the local variables and parameters of the method in which it's defined. For example:

static void Main()
{
int factor = 2;
Func<int, int> multiplier = n => n * factor;
Console.WriteLine (multiplier (3)); // 6
}

Local variables and parameters referenced by a lambda expression are called outer variables or captured variables. A lambda expression that includes outer variables is called a closure.

Outer variables are evaluated when the delegate is actually invoked, not when the variables are captured:

int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (2)); // 20

Lambda expressions can update captured variables:

int seed = 0;
Func<int> natural = () => seed++;
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 1

Outer variables have their lifetimes extended to that of the delegate. In the following example, the local variable seed would ordinarily disappear from scope when Natural finished executing. But because seed has been captured, its lifetime is extended to that of the capturing delegate, natural:


static Func<int> Natural()
{
int seed = 0;
return () => seed++; // Returns a closure
}

static void Main()
{
Func<int> natural = Natural();
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 1
}

A local variable instantiated within a lambda expression is unique per invocation of the delegate instance. If we refactor our previous example to instantiate seed within the lambda expression, we get a different (in this case, undesirable) result:

static Func<int> Natural()
{
return() => { int seed = 0; return seed++; };
}

static void Main()
{
NumericSequence natural = Natural();
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 0
}

0 comments


Subscribe to Developer Techno ?
Enter your email address:

Delivered by FeedBurner