Expression Lambdas

The statement lambda syntax is already much less verbose than the corresponding method declaration; as we’ve seen, it need not declare the method’s name, accessibility, return type, or parameter types. Nevertheless, we can get even less verbose by using an expression lambda. In Listing 13.12, Listing 13.13, and Listing 13.14, we saw statement lambdas whose blocks consisted of a single return statement. What if we eliminated the ceremony around that? The only relevant information in such a lambda block is the expression that is returned. An expression lambda contains only that returned expression, with no statement block at all. Listing 13.16 is the same as Listing 13.12, except that it uses an expression lambda rather than a statement lambda.

Listing 13.16: Passing a Delegate with an Expression Lambda
//...
DelegateSample.BubbleSort
    (items, (first, second) => first < second);
//...

Generally, you would read the lambda operator => in an expression lambda the same way as you would a statement lambda: as goes to or becomes, although, in those cases where the delegate is a predicate, it is common to read the lambda operator as such that or where. Thus, you might read the lambda in Listing 13.16 as “first and second such that first is less than second.”

Like the null literal, an anonymous function does not have any type associated with it; rather, its type is determined by the type it is being converted to. In other words, the lambda expressions we’ve seen so far are not intrinsically of the Func<int, int, bool> (or Comparer) type, but they are compatible with that type and may be converted to it. As a result, you cannot use the typeof() operator on an anonymous method, and calling GetType() is possible only after you convert the anonymous method to a particular type.

Table 13.1 provides additional lambda expression characteristics.

Table 13.1: Lambda Expression Notes, Errors, and Examples

Statement

Example

Starting with C# 9.0, lambda expressions supported discard parameters. (For backwards compatibility, a single discard results in a variable declaration named _.)

Action<int, int> x = (_,_)=>

  Console.WriteLine("This is a test.");

Starting with C# 10.0, a lambda expression has a definite type, so they can be used with an anonymous type declaration.

// You can assign lambda

// expression to an implicitly

// typed local variable starting C# 10.

var v = (int x) => x;

Starting with C# 10.0, lambda expressions may have a return type, even a return type of void. This is especially helpful when the delegate type cannot be inferred—such as when returning null.

Action action = void () => { };

var func

     = short? (long number) =>

         number<=short.MaxValue?

             (short)number:null;

There are no members that can be accessed directly from a lambda expression—not even the methods of object.

// ERROR: Operator "." cannot be applied to

// operand of type "lambda expression"

string s = ((int x) => x).ToString();

Lambda expressions do not have a type and so cannot appear to the left of an is operator.

// ERROR: The first operand of an "is" or "as"

// operator may not be a lambda expression or

// anonymous method

bool b = ((int x) => x) is Func<int, int>;

A lambda expression can be converted only to a compatible delegate type; here an int-returning lambda may not be converted to a delegate type that represents a bool-returning method.

// ERROR: Lambda expression is not compatible

// with Func<int, bool> type

Func<int, bool> f = (int x) => x;

Jump statements (break, goto, continue) inside lambda expressions cannot be used to jump to locations outside the lambda expression, and vice versa. Here the break statement inside the lambda would jump to the end of the switch statement outside the lambda.

// ERROR: Control cannot leave the body of an

// anonymous method or lambda expression

string[] args;

Func<string> f;

switch(args[0])

{

   case "/File":

       f = () =>

       {

           if (!File.Exists(args[1]))

               break;

           return args[1];

       };

       // ...

}

Parameters and locals introduced by a lambda expression are in scope only within the lambda body.

// ERROR: The name "first" does not

// exist in the current context

Func<int, int, bool> expression =

     (first, second) => first > second;

first++;

The compiler’s definite assignment analysis is unable to detect initialization of “outer” local variables in lambda expressions.

int number;

Func<string, bool> f =

   text => int.TryParse(text, out number);

if (f("1"))

{

   // ERROR: Use of unassigned local variable

   System.Console.Write(number);

}

int number;

Func<int, bool> isFortyTwo =

   x => 42 == (number = x);

if (isFortyTwo(42))

{

     // ERROR: Use of unassigned local variable

     Console.Write(number);

}

{{ snackbarMessage }}