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.
Anonymous methods support one small feature that is not supported in lambda expressions: Anonymous methods may omit their parameter list entirely in some circumstances.
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.
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.
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.
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>.)
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.
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.
________________________________________