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# 3.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:
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;};
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;
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 <TResult> (); 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
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 }