Anonymous Methods

An anonymous method is like a statement lambda,10 but without many of the features that make lambdas so compact. An anonymous method must explicitly type every parameter, and it must have a statement block. Rather than using the lambda operator => between the parameter list and the code block, an anonymous method puts the keyword delegate before the parameter list, emphasizing that the anonymous method must be converted to a delegate type. Listing 13.17 shows the code from Listing 13.7, Listing 13.12, and Listing 13.15 rewritten to use an anonymous method.

Listing 13.17: Passing an Anonymous Method in C# 2.0
//...
DelegateSample.BubbleSort(items,
    delegate(int first, int second)
    {
        return first < second;
    }
);
//...
Guidelines
AVOID the anonymous method syntax in new code; prefer the more compact lambda expression syntax.

Anonymous methods support one small feature that is not supported in lambda expressions: Anonymous methods may omit their parameter list entirely in some circumstances.

AdVanced Topic
Parameterless Anonymous Methods

Unlike lambda expressions, anonymous methods may omit the parameter list entirely provided that the anonymous method body does not use any parameter and the delegate type requires only “value” parameters (i.e., it does not require the parameters to be marked as out or ref). For example, the anonymous method expression delegate { return Console.ReadLine() != ""; } is convertible to any delegate type that requires a return type of bool regardless of the number of parameters the delegate requires. This feature is not used frequently, but you might encounter it when reading legacy code.

AdVanced/Beginner Topic
Why “Lambda” Expressions?

It is fairly obvious why anonymous methods are so named: They look very similar to method declarations but do not have a declared name associated with them. But where did the lambda in “lambda expressions” come from?

The idea of lambda expressions comes from the work of the logician Alonzo Church, who in the 1930s invented a technique called the lambda calculus for studying functions. In Church’s notation, a function that takes a parameter x and results in an expression y is notated by prefixing the entire expression with a small Greek letter lambda and separating the parameter from the value with a dot. The C# lambda expression x=>y would be notated λx.y in Church’s notation. Because it is inconvenient to use Greek letters in C# programs and because the dot already has many meanings in C#, the designers of C# chose to use the “fat arrow” notation rather than the original notation. The name lambda expression indicates that the theoretical underpinnings of the idea of anonymous functions are based on the lambda calculus, even though no letter lambda actually appears in the text.

Delegates Do Not Have Structural Equality

Delegate types in .NET do not exhibit structural equality. That is, you cannot convert a reference to an object of one delegate type to an unrelated delegate type, even if the formal parameters and return types of both delegates are identical. For example, you cannot assign a reference to a Comparer to a variable of type Func<int, int, bool>, even though both delegate types represent methods that take two int parameters and return a bool. Unfortunately, the only way to use a delegate of a given type when a delegate of a structurally identical but unrelated delegate type is needed is to create a new delegate that refers to the Invoke method of the old delegate. For example, if you have a variable c of type Comparer, and you need to assign its value to a variable f of type Func<int, int, bool>, you can say f = c.Invoke;.

However, thanks to variance support,11 it is possible to make reference conversions between some delegate types. Consider the following contravariant example: Because void Action<in T>(T arg) has the in type parameter modifier, it is possible to assign a reference to a delegate of type Action<object> to a variable of type Action<string>.

Many people find delegate contravariance confusing. It may help to remember that an action that can act on every object can be used as an action that acts on any string. But the opposite is not true: An action that can act only on strings cannot act on every object. Similarly, every type in the Func family of delegates is covariant in its return type, as indicated by the out type parameter modifier on TResult. Therefore, it is possible to assign a reference to a delegate of type Func<string> to a variable of type Func<object>.

Listing 13.18 shows examples of delegate covariance and contravariance.

Listing 13.18: Using Variance for Delegates
// Contravariance
Action<object> broadAction =
    (object data) =>
    {
        Console.WriteLine(data);
    };
 
Action<string> narrowAction = broadAction;
 
// Covariance
Func<string?> narrowFunction =
    () => Console.ReadLine();
Func<object?> broadFunction = narrowFunction;
 
// Contravariance and covariance combined
Func<objectstring?> func1 =
    (object data) => data.ToString();
 
Func<stringobject?> func2 = func1;

The last part of the listing combines both variance concepts into a single example, demonstrating how they can occur simultaneously if both in and out type parameters are involved.

Allowing reference conversions on generic delegate types was a key motivating scenario for adding covariant and contravariant conversions.12 (The other was support for covariance to IEnumerable<out T>.)

AdVanced Topic
Lambda Expression and Anonymous Method Internals

Lambda expressions (and anonymous methods) are not intrinsically built into the CLR. Rather, when the compiler encounters an anonymous function, it translates it into special hidden classes, fields, and methods that implement the desired semantics. The C# compiler generates the implementation code for this pattern so that developers do not have to code it themselves. When given the code in Listing 13.12, Listing 13.13, Listing 13.16, or Listing 13.17, the C# compiler generates CIL code that is similar to the C# code shown in Listing 13.19.

Listing 13.19: C# Equivalent of CIL Generated by the Compiler for Lambda Expressions
public class DelegateSample
{
    // ...
 
    public static void Main()
    {
        int[] items = new int[5];
 
        for(int i = 0; i < items.Length; i++)
        {
            Console.Write("Enter an integer: ");
            string? text = Console.ReadLine();
            if (!int.TryParse(text, out items[i]))
            {
                Console.WriteLine($"'{text}' is not a valid integer.");
                return;
            }
        }
 
        BubbleSort(items,
            __AnonymousMethod_00000000);
 
 
        for (int i = 0; i < items.Length; i++)
        {
            Console.WriteLine(items[i]);
        }
 
    }
    private static bool __AnonymousMethod_00000000(
        int first, int second)
    {
        return first < second;
    }
}

In this example, the compiler transforms an anonymous function into a separately declared static method, which is then instantiated as a delegate and passed as a parameter. Unsurprisingly, the compiler generates code that looks remarkably like the original code in Listing 13.8, which the anonymous function syntax was intended to streamline. However, the code transformation performed by the compiler can be considerably more complex than merely rewriting the anonymous function as a static method if outer variables are involved.

________________________________________

10. Lambda expressions were not yet available in C# 2.0.
11. Added in C# 4.0.
12. Added to C# 4.0.
{{ snackbarMessage }}