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
(we describe the process in Chapter 8 of C#
5.0 in a Nutshell).
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
Internally, the compiler resolves lambda expressions of this type by writing a private method, and moving the expression’s code into that method.
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.
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; };
Lambda expressions are used most commonly with the Func
and Action
delegates, so you will most often see our
earlier expression written as follows:
Func<int,int> sqr = x => x * x;
The compiler can usually infer the type of lambda parameters contextually. When this is not the case, you can specify parameter types compiler:
Func<int,int> sqr = (int
x) => x * x;
Here’s an example of an expression that accepts two parameters:
Func<string,string,int> totalLength = (s1, s2) => s1.Length + s2.Length; int total = totalLength ("hello", "world"); // total=10;
Assuming Clicked
is an event of
type EventHandler
, the following
attaches an event handler via a lambda expression:
obj.Clicked += (sender,args) => Console.Write ("Click");
A lambda expression can reference the local variables and parameters of the method in which it’s defined (outer variables). For example:
static void Main() { intfactor
= 2; Func<int, int> multiplier = n => n *factor
; Console.WriteLine (multiplier (3)); // 6 }
Outer variables referenced by a lambda expression are called captured variables. A lambda expression that captures variables is called a closure. Captured variables are evaluated when the delegate is actually invoked, not when the variables were captured:
int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
Lambda expressions can themselves update captured variables:
int seed = 0; Func<int> natural = () => seed++; Console.WriteLine (natural()); // 0 Console.WriteLine (natural()); // 1 Console.WriteLine (seed); // 2
Captured 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
}
When you capture an iteration variable in a for
loop, C# treats the iteration variable
as though it was declared outside the loop. This
means that the same variable is captured in each
iteration. The following program writes 333
instead of writing 012
:
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
actions [i] = () => Console.Write (i)
;
foreach (Action a in actions) a(); // 333
Each closure (shown in boldface) captures the same variable,
i
. (This actually makes sense when
you consider that i
is a variable
whose value persists between loop iterations; you can even explicitly
change i
within the loop body if
you want.) The consequence is that when the delegates are later
invoked, each delegate sees i
’s
value at the time of invocation—which is 3. The
solution, if we want to write 012
,
is to assign the iteration variable to a local variable that’s scoped
inside the loop:
Action[] actions = new Action[3]; for (int i = 0; i < 3; i++) { intloopScopedi = i
; actions [i] = () => Console.Write (loopScopedi
); } foreach (Action a in actions) a(); // 012
This causes the closure to capture a different variable on each iteration.