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<T>, representing the code inside the lambda expression in a traversable object model. This allows the lambda expression to be interpreted later at runtime.
In the following example,
square is assigned the lambda expression x = > x * x:delegate int Transformer (int i);class Test{ static void Main( ) { Transformer square = x => x * x; Console.WriteLine (square(3)); // 9 }}We could rewrite the example by converting the lambda expression into a method, and then call the method through the delegate. In fact, the compiler internally performs that translation for you when you assign a delegate a lambda expression:
delegate int Transformer (int i);class Test{ static void Main( ) { Transformer square = Square; Console.WriteLine (square(3)); // 9 } static int Square (int x) {return x * x;}}A lambda expression has the following BNF form:
(parameters) => expression-or-statement-blockFor 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 specify the type of each parameter explicitly. 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 theFunc and Action delegates, defined in the System namespace:delegate TResult Func <T> ( );delegate TResult Func <T,TResult> (T1 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);delegate void Action ( );delegate void Action <T> (T1 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:class Test{ static void Main( ) { Func<int,int> square = x => x * x; Console.WriteLine (square(3)); // 9 }}Outer Variables
A lambda expression can reference the local variables and parameters of the method in which it's defined. For example:delegate int NumericSequence ( );class Test{ static void Main( ) { int seed = 0; NumericSequence natural = ( ) => seed++; Console.WriteLine (natural( )); // 0 Console.WriteLine (natural( )); // 1 }}Local variables and parameters referenced by a lambda expression are called outer variables. In our example, seed is an outer variable referenced by the lambda expression
( ) => seed++. Outer variables are captured, meaning their lifetime is extended to that of the lambda expression. Let's refactor the example to make the effect of capturing more striking: delegate int NumericSequence ( );class Test{ static NumericSequence Natural ( ) { int seed = 0; // executes once (per call to Natural( )) return ( ) => seed++; // executes twice (per call to delegate instance // returned by Natural( )) } static void Main( ) { NumericSequence natural = Natural ( ); Console.WriteLine (natural( )); // 0 Console.WriteLine (natural( )); // 1 }}The local variable seed would ordinarily just pop off the stack when the Natural method exits. However, seed is captured by the lambda expression of the delegate instance returned by Natural. This means the lifetime of seed is extended to the lifetime of that delegate instance. Subsequent invocations of that same delegate instance will reuse the same seed variable.
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:
delegate int NumericSequence ( );class Test{ static NumericSequence Natural ( ) { return ( ) => {int seed = 0; return seed++; }; } static void Main( ) { NumericSequence natural = Natural ( ); Console.WriteLine (natural( )); // 0 Console.WriteLine (natural( )); // 0 }}