Lambda Expressions

In Listing 13.7 and Listing 13.11, we saw that you can convert the expressions GreaterThan and AlphabeticalGreaterThan to a delegate type that is compatible with the parameter types and the return type of the named method. You might have noticed that the declaration of the GreaterThan method—the code that says it is a public, static, bool-returning method with two parameters of type int named first and second—was considerably larger than the body of the method, which simply compared its two parameters and returned the result. It is unfortunate that so much ceremony must surround such a simple method merely so that it can be converted to a delegate type.

To address this concern, compact syntax exists for creating a delegate. When referring generally to anonymous methods or lambda expressions, we’ll refer to them as anonymous functions. Both syntaxes are still legal, but for new code, the lambda expression syntax is preferred over the anonymous method syntax.8

Lambda expressions are themselves divided into two types: statement lambdas and expression lambdas. Figure 13.2 shows the hierarchical relationship between these terms.

Figure 13.2: Anonymous function terminology
Statement Lambdas

The purpose of a lambda expression is to eliminate the hassle of declaring an entirely new member when you need to make a delegate from a very simple method. Several different forms of lambda expressions exist. A statement lambda, for example, consists of a formal parameter list, followed by the lambda operator =>, followed by a code block.

Listing 13.12 shows equivalent functionality to the call to BubbleSort from Listing 13.8, except that Listing 13.12 uses a statement lambda to represent the comparison method rather than creating a GreaterThan method. As you can see, much of the information that appeared in the GreaterThan method declaration is now included in the statement lambda; the formal parameter declarations and the block are the same, but the method name and its modifiers are missing.

Listing 13.12: Creating a Delegate with a Statement Lambda
//...
 
BubbleSort(items,
    (int first, int second) =>
    {
        return first < second;
    }
);
 
//...

When reading code that includes a lambda operator, you would replace the lambda operator with the words go/goes to. For example, in Listing 13.12, you would read the second BubbleSort() parameter as “integers first and second go to returning the result of first less than second.”

Notice that the syntax in Listing 13.12 is almost identical to that in Listing 13.8, apart from the fact that the comparison method is now found lexically where it is converted to the delegate type rather than being found elsewhere and looked up by name. The name of the method is missing, which explains why such methods are called anonymous functions. The return type is missing, but the compiler can infer that the lambda expression is being converted to a delegate whose signature requires the return type bool. The compiler verifies that the expressions of every return statement in the statement lambda’s block would be legal in a bool-returning method. The public modifier is missing; given that the method is no longer an accessible member of the containing class, there is no need to describe its accessibility. Similarly, the static modifier is no longer necessary. The amount of ceremony around the method is already greatly reduced.

The syntax is still needlessly verbose, however. We have deduced from the delegate type that the lambda expression must be bool-returning; we can similarly deduce that both parameters must be of type int, as shown in Listing 13.13.

Listing 13.13: Omitting Parameter Types from Statement Lambdas
//...
DelegateSample.BubbleSort(items,
    (first, second) =>
    {
        return first < second;
    }
);
//...

In general, explicitly declared parameter types are optional in all lambda expressions if the compiler can infer the types from the delegate that the lambda expression is being converted to. For situations when specifying the type makes code more readable, however, C# enables you to do so. In cases where inference is not possible, the C# language requires that the lambda parameter types be stated explicitly. If one lambda parameter type is specified explicitly, then all of them must be specified explicitly, and they must all match the delegate parameter types exactly.

Guidelines
CONSIDER omitting the types from lambda formal parameter lists when the types are obvious to the reader or when they are an insignificant detail.

One other means of reducing the syntax is possible, as shown in Listing 13.14: A lambda expression that has exactly one parameter whose type is inferred may omit the parentheses around the parameter list. If there are zero parameters or more than one parameter, or if the single parameter is explicitly typed, the lambda must have parentheses around the parameter list.

Listing 13.14: Statement Lambdas with a Single Input Parameter
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
 
// ...
    IEnumerable<Process> processes = Process.GetProcesses().Where(
        process => { return process.WorkingSet64 > 1000000000; });
    // ...

In Listing 13.14, the Where() method returns a query for processes that have a physical memory utilization greater than 1 billion bytes.

Starting in C# 9.0, you can use discards (_) for one or more parameters for all anonymous functions assuming they are not needed for implementation.9

Contrast this with Listing 13.15, which has a parameterless statement lambda. The empty parameter list requires parentheses. Also note that in Listing 13.15, the body of the statement lambda includes multiple statements inside the statement block (via curly braces). Although a statement lambda can contain any number of statements, typically a statement lambda uses only two or three statements in its statement block.

Listing 13.15: Parameterless Statement Lambdas
//...
Func<string> getUserInput =
    () =>
    {
        string? input;
        do
        {
            input = Console.ReadLine();
        }
        while(!string.IsNullOrWhiteSpace(input));
        return input!;
    };
//...

________________________________________

8. Throughout this book, we generally use the lambda expression syntax except when specifically describing C# 2.0 anonymous methods.
9. If there is only one parameter, then the underscore will result in a variable declaration for backwards compatibility.
{{ snackbarMessage }}
;