Introduce to C# anonymous methods

URL:http://www.theserverside.net/tt/articles/showarticle.tss?id=AnonymousMethods

Introduction

Generics are often introduced as the mainstream language feature of Whidbey (.NET2). However, while surfing off the beaten path I realized that new features named anonymous methods and iterators are also very interesting. Unlike generics, these two features don't imply new IL instructions set changes compared to .NET1 IL instructions set or any CTS changes. All the magic is in the compilers. This article is the first of a series of two articles dedicated to these new features. While these articles unfold, you will understand the reasons for beginning with anonymous methods. Both articles share the same structure: first a basic introduction to the functionality, followed by a strong analysis of the compiler work before drilling into advanced uses.


width="300" scrolling="no" height="250" frameborder="0" marginwidth="0" marginheight="0" border="0" src="http://ad.doubleclick.net/adi/atssn/tt;bkg=FFFFFF;kw=;dcopt=;pos=1;sz=300x250;ptile=1;ord=8192936?"> <a href='http://ad.doubleclick.net/jump/atssn/tt;bkg=FFFFFF;kw=;dcopt=;pos=1;sz=300x250;ptile=1;ord=8192936?''> <img src='http://ad.doubleclick.net/ad/atssn/tt;bkg=FFFFFF;kw=;dcopt=;pos=1;sz=300x250;ptile=1;ord=8192936?'' width='300' height='250' border='0'></a>

Introduction to anonymous methods in C# v2

A simple example

Let's begin by enhancing some C# v1 code to use C# v2 anonymous methods. Here is a simple C# v1 program that first references and then invokes a method, through a delegate.

 System;
{
();
GetMethod(){
(MethodBody);
}
MethodBody(){
.WriteLine();
}
Main(){
delegateInstance = GetMethod();
delegateInstance();
delegateInstance();
.ReadKey();
}
}

Here is the same program rewritten with a C# v2 anonymous method:

 System;
{
();
GetMethod(){
(){ .WriteLine();};
}
Main(){
delegateInstance = GetMethod();
delegateInstance();
delegateInstance();
.ReadKey();
}
}

You should notice that:

  • The delegate keyword has a new use in C# v2. When the C# v2 compiler found the delegate keyword inside a method body, it expects that it is followed by an anonymous method body.
  • It is possible to assign an anonymous method to a delegate reference
  • We understand why this feature is named anonymous method: the method defined in the body of GetMethod() is not named. Nevertheless, it is possible to invoke it because it is referenced by a delegate instance.

You should notice as well that it is possible to use the operator += to allow a delegate instance to reference several methods (anonymous or not):

 System;
{
();
static void Main(){
delegateInstance = () { .WriteLine(); };
delegateInstance += () { .WriteLine(); };
delegateInstance();
.ReadKey();
}
}

As you might expect this program outputs:

Hello
Bonjours

Anonymous methods can accept some arguments

As shown in the following example, an anonymous method can accept some arguments of any type. You can also use keywords ref and out to tune how arguments are passed:

 System;
{
( valTypeParam, refTypeParam, refParam, outParam);
GetMethod(){
( valTypeParam, refTypeParam, refParam, outParam)
{
.WriteLine(, valTypeParam, refTypeParam);
refParam++;
outParam = 9;
valTypeParam;
};
}
Main(){
delegateInstance = GetMethod();
refVar = 5;
outVar;
i = delegateInstance(1, , refVar, outVar);
j = delegateInstance(2, , refVar, outVar);
.WriteLine(, i, j, refVar, outVar);
.ReadKey();
}
}

This program outputs:

Hello valParam:1 refTypeParam:one
Hello valParam:2 refTypeParam:two
i:1 j:2 refVar:7 outVar:9

As you can see, the returned type is not defined inside the anonymous method declaration. The returned type of an anonymous method is inferred by the C# v2 compiler from the returned type of the delegate to which it is assigned. This type is always known because the compiler forces assignment of any anonymous method to a delegate.

An anonymous method can't be tagged with an attribute. This restriction implies that you can't use the param keyword in the list of arguments of an anonymous method. Indeed, using the keyword param forces the compiler to tag the concerned method with the ParamArray attribute.

 System;
{
( [] arr);
GetMethod(){
( [] arr){
.WriteLine();
};
}

}

A syntax subtlety

It is possible to declare an anonymous method without any signature, i.e. you are not compelled to write a pair of parenthesis after the keyword delegate if your anonymous method doesn't take any argument. In this case, your method can be assigned to any delegate instance that returns a void type and that doesn't have an out argument. Obviously, such an anonymous method doesn't have access to parameters that are provided through its delegate invocation.

 System;
{
( valTypeParam, refTypeParam, refParam);
Main(){
delegateInstance = { .WriteLine();};
refVar = 5;
delegateInstance(1, , refVar);
delegateInstance(2, , refVar);
.ReadKey();
}
}

Anonymous methods and generics

As shown in the example below, an argument of an anonymous method can have a generic type:

 System;
<T>{
(T t);
UneMethode(T t){
Type Instance = (T arg){ .WriteLine(,arg.ToString());};
Instance(t);
}
}

{
Main(){
<> inst = <>();
inst.UneMethode(5.5);
.ReadKey();
}
}

In.NET v2, a delegate type can be declared with some generics arguments. An anonymous method can be assigned to a delegate instance of such a type. You just have to resolve type parameters on both side of the assignment:

 System;
{
(T t);
static void Main(){
<> Instance = ( arg) { .WriteLine(, arg.ToString()); };
Instance(5.5);
.ReadKey();
}
}

Use of anonymous methods in the real world

Anonymous methods are particularly suited to define ‘small' methods that must be invoked ‘by design' through a delegate. For instance, you might use an anonymous method to code the entry point procedure of a thread:

 System;
System.Threading;
{
Main(){
thread = ((){
.WriteLine(,.CurrentThread.GetHashCode());
});
thread.Start();
.WriteLine(, .CurrentThread.GetHashCode());
.ReadKey();
}
}

This program outputs:

ThreadHashCode:1 Bonjour
ThreadHashCode:3 Hello

Another classic example of this kind of use lies in the winform's controls callback procedures:

  : Form{
Button m_Button;
(){
InitializeComponent();
m_Button.Click += ( sender, EventArgs args){
MessageBox.Show();
};
}
InitializeComponent()
{...}
}

It seems that anonymous method looks like a tiny language enhancement. It's now time to dig under the hood to realize that anonymous methods are far more complex and can be far more useful.

The C# v2 compiler and anonymous methods

The easy way

As you might expect, when an anonymous method is compiled, an extra method is spawned by the compiler in the concerned class.

 System;
{
();
Main(){
delegateInstance = () { .WriteLine(); };
delegateInstance();
.ReadKey();
}
}

Thus, the following assembly is the compiled version of the previous program (the assembly is viewed with the tool Reflector provided freely by Lutz Roeder. Reflector supports right now .NET2 assemblies). :

Indeed, a new private and static method named

b__0() has been automatically generated. We check that this method has the body of our anonymous method. If our anonymous method was declared inside an instance method, the generated method would have been an instance method.

We also note that a delegate field named <>9_CachedAnonymousMethoddelegate1 of type delegateType has been generated to reference our anonymous method.

It is interesting to note that all these generated members can't be viewed with the C# intellisense because their names contain a pair of angle brackets < >. Such names are valid for the CLR syntax but incorrect for the C# syntax.

Captured local variables

To keep things clear and simple, we haven't mentioned yet the fact that an anonymous method can have access to a local variable of its outer method. Let's analyze this possibility through the following example:

 System;
{
();
MakeCounter(){
counter = 0;
delegateInstanceCounter = { ++counter; };
delegateInstanceCounter;
}
Main(){
TypeCounter counter1 = MakeCounter();
TypeCounter counter2 = MakeCounter();
.WriteLine(counter1());
.WriteLine(counter1());
.WriteLine(counter2());
.WriteLine(counter2());
.ReadLine();
}
}

This program outputs:

1
2
1
2

Think about it, it might stump you:

  • The local variable counter seems to survive when the thread leaves the method MakeCounter().
  • It seems that several occurrences of this ‘surviving' local variable exist!

Note that in .NET 2005, the CLR and the IL language haven't been tweaked to support the anonymous method feature. The mystery might stems from the compiler. It's a nice example of ‘syntactic sugar'. Let's analyse the assembly:

This analysis makes things clear because:

  • The compiler doesn't only create a new method as we saw in the previous section. It utterly creates a new class named <>c__DisplayClass1 in this example.
  • This class has an instance method called b__0(). This method has the body of our anonymous method.
  • This class has also an instance field called counter. This field keeps track of the state of the local variable counter. We say the local variable counter has been captured by the anonymous method.
  • The method instantiates the class <>c__DisplayClass1. Moreover it initializes the field counter of the created instance.
  • Notice that the method MakeCounter() doesn't have any local variable. For the variable counter, it uses the same named field of the generated instance of the class <>c__DisplayClass1.

Before explaining why the compiler has this surprising behaviour, let's go further to get a thorough understanding of its work.

Captured local variables and code visibility

The following subtle example has been mentioned in the blog of Brad Abrams.

 System;
System.Threading;
{
Main([] args){
( i = 0; i < 5; i++)
.QueueUserWorkItem( { .WriteLine(i); }, );
.ReadLine();
}
}

This program outputs in a non-deterministic way something like:

0
1
5
5
5

This result compels us to infer that the local variable i is shared amongst all threads. The execution is non-deterministic because the Main() method and our closure are executed simultaneously by several threads. To make things clear, here is the decompiled code of the Main() method:

 Main([] args){
flag1;
.<>c__DisplayClass1 class1 = .<>c__DisplayClass1();
class1.i = 0;
Label_0030;
Label_000F:
.QueueUserWorkItem( (class1.
b__0), );
class1.i++;
Label_0030:
flag1 = class1.i < 5;
(flag1){
Label_000F;
}
.ReadLine();
}

Notice that the fact that the value 5 is printed indicates that the Main() method is out of the loop at the moment of printing.
The following version of this program has a deterministic execution:

 System;
System.Threading;
{
Main([] args){
for ( i = 0; i < 5; i++){
j = i;
.QueueUserWorkItem( { .WriteLine(j); }, );
}
.ReadLine();
}
}

This time, the program outputs:

0
1
2
3
4

This behavior stems from the fact that the local variable j is captured for each iteration. Here is the decompiled code of the Main() method:

 Main([] args){
.<>c__DisplayClass1 class1;
flag1;
num1 = 0;
Label_0029;
Label_0004:
class1 = .<>c__DisplayClass1();
class1.j = num1;

.QueueUserWorkItem( (class1.
b__0), );
num1++;
Label_0029:
flag1 = num1 < 5;
(flag1){
Label_0004;
}
.ReadLine();
}

This sheds light on the fact that capturing local variables with anonymous methods is not an easy thing. You should always take care when using this feature.
Note that a captured local variable is no longer a local variable. If you access such a variable with some unsafe code, you might have fixed it before (with the C# keyword fixed).

Captured arguments

Arguments of a method can always be deemed as local variables. Therefore, C# v2 allows an anonymous method to use arguments of its outer method. For instance:

 System;
{
();
MakeCounter( counterName){
counter = 0;
delegateInstanceCounter = {
.WriteLine(counterName + (++counter).ToString());
};
delegateInstanceCounter;
}
Main(){
counterA = MakeCounter();
counterB = MakeCounter();
counterA();
counterA();
counterB();
counterB();
.ReadLine();
}
}

This program outputs:

Counter A:1
Counter A:2
Counter B:1
Counter B:2

Nevertheless, an anonymous method can't capture an out or ref argument. This restriction is readily understandable as soon as you realize that such an argument can't be seen as a local variable. Indeed, such an argument survives the execution of the method.

Captured fields

An anonymous method can access members of its outer class. The case of static member's access is readily understandable since there is one and only one occurrence of any static field in the domain application. Thus, there is nothing like ‘capturing' a static field.

An instance member's access is less obvious. To clarify this point, remember that the this reference that allows access to instance members, is a local variable of the outer instance method. Therefore, the this reference is captured by the anonymous method. Let's analyze the following example:

 System;
();
{
m_Name;
( name) { m_Name = name; }
MakeCounter( counterName){
counter = 0;
delegateInstanceCounter = {
.Write(counterName +(++counter).ToString());
.WriteLine( + m_Name); // on aurait pu écrire this.m_Name
};
DelegateInstanceCounter;
}
}
{
Main(){
counterMaker1 = ();
counterMaker2 = ();
counterA = counterMaker1.MakeCounter();
counterB = counterMaker1.MakeCounter();
counterC = counterMaker2.MakeCounter();
counterA(); counterA();
counterB(); counterB();
counterC(); counterC();
.ReadLine();
}
}

This program outputs:

Counter A:1 Counter built by:Factory1
Counter A:2 Counter built by:Factory1
Counter B:1 Counter built by:Factory1
Counter B:2 Counter built by:Factory1
Counter C:1 Counter built by:Factory2
Counter C:2 Counter built by:Factory2

Let's decompile the MakeCounter() method to expose the this reference capture:

internal  MakeCounter( counterName){
.<>c__DisplayClass1 class1 = .<>c__DisplayClass1();
class1.<>4__this = ;
class1.counterName = counterName;
class1.counter = 0;
(class1.b__0);
}

Notice that the this reference cannot be captured by an anonymous method that is defined in a structure. Here is the compiler error:

Advanced uses of anonymous methods

Definitions: closure and lexical environment

A closure is a function that captures values of its lexical environment, when it is created at run-time. The lexical environment of a function is the set of variables visible from the concerned function.

In previous definitions, we used carefully the terms when and from. It indicates that the notion of closure pinpoints something that exists at run-time (as the concept of object). It indicates also that the notion of lexical environment pinpoints something that exists in the code, i.e at compile-time (as the concept of class). Consequently, you can consider that the lexical environment of a C# v2 anonymous method is the class generated by the compiler. Following the same idea, you can consider that an instance of such a generated class is a closure.

The definition of closure also implies the notion of creating a function at run-time. Mainstream imperative languages such as C, C++, C#1, Java or VB.NET1 don't support the ability to create an instance of a function at run-time. This feature stems from functional languages such as Haskell or Lisp. Thus C# v2 goes beyond imperative languages by supporting closures. However, C# v2 is not the first imperative language that supports closures since Perl and Ruby also have this feature.

Ramblings on closures

A function computes its results both from values of its arguments and from the context that surrounds its invocation. You can consider this context as a set of background data. Thus, arguments of a function can be seen as foreground data. Therefore, the decision that an input data of a function must be an argument must be taken from the relevance of the argument for the computation.

Generally, when using object languages, the context of a function (i.e. the context of an instance method) is the state of the object on which it is invoked. When programming with non object oriented imperative languages such as C, the context of a function is the values of global variables. When dealing with closures, the context is the values of captured variables when the closure is created. Therefore, as classes, closures are a way to associate behavior and data. In object oriented world, methods and data are associated thanks to the this reference. In functional world a function is associated with the values of captured variables. To make thinks clear:

  • You can think of an object as a set of method attached to a set of data.
  • You can think of a closure as a set of data attached to a function.

Using closures instead of classes

The previous section implicitly implies that some sort of classes could be replaced by some anonymous methods. Actually, we already perform such replacement in our implementation of counter. The behavior is the increment of the counter while the state is its value. However, the counter implementation doesn't harness the possibility to pass argument to an anonymous method. The following example shows how to harness closures to perform parameterized computation on the state of an object:

 System;
{
( integerToMultiply);
BuildMultiplier( multiplierParam){
( integerToMultiply){
egerToMultiply *= multiplierParam;
};
}
Main(){
multiplierPar8 = BuildMultiplier(8);
multiplierPar2 = BuildMultiplier(2);
aninteger = 3;
multiplierPar8( aninteger);

multiplierPar2( aninteger);

.ReadLine();
}
}

Here is another example that shows how to harness closures to perform parameterized computation to obtain a value from the state of an object:

 System;
{
( price) { m_Price = price; }
m_Price;
Price { { m_Price; } }
}
class Program{
( article);
static BuildTaxComputer( tva){
( article){
(article.Price * (100 + tva)) / 100;
};
}
static void Main(){
taxComputer19_6 = BuildTaxComputer(19.6m);
taxComputer5_5 = BuildTaxComputer(5.5m);
article = (97);
.WriteLine("Price (Tax=19.6%) : "+ taxComputer19_6(article));
.WriteLine("Price (Tax=5.5%) : "+ taxComputer5_5(article));
.ReadLine();
}
}

Using closures as functors

For those who have fiddled with the C++'s Standard Template Library (STL), functors as you might recall are an elegant programming style that allows you to tweak a collection in any way with a single line of code. The good news is that anonymous methods make this feature a reality in the .NET world.

The name functor stands for function-object. Basically, a functor is a parameterized behaviour. Functors are particularly suited to manipulate collections because you can apply its behaviour to all elements of a collection with a single line of code. For instance, suppose that you have a list of articles and that you want to select articles that cost more than a given price. You would write:

<> SelectExpensiveArticle( expensiveThreshold, <> listIn){
<> listOut = <>();
( article listIn)
( article.Price > expensiveThreashold )
listOut.Add(article);
listOut;
}

With C# v2's functors you can code the same behaviour like this:

<> SelectExpensiveArticle2( expensiveThreshold,
<> listIn){
listIn.FindAll(( article) { (article.Price >
expensiveThreashold); });

}

Functors are good at four things:

  • Selecting elements of a list that satisfy a given predicate (in the previous example, the predicate is: Is it true that the price is more expensive that the given threshold?).
  • Performing an action on every element of a list.
  • Sorting elements of a list.
  • Building a new list from a list. Both lists have the same number of elements. Each element of the new list is computed from its corresponding element in the former list. Elements of the new list can have a different type than the type of elements of the former list.

Here is a small program that illustrates these functionalities:

 System.Collections.Generic;

{
{
( price, name){Price = price;Name = name;}
Price;
Name;
}
Main(){


List<> egers = List<>();
( i=1; i<=10; i++) egers.Add(i);

<> articles = <>();
articles.Add( (23,));
articles.Add( (56,));




List<> even = integers.FindAll(( i){ i%2==0; });




sum = 0;
integers.ForEach(( i) { sum += i; });




increment = 10;
articles.ForEach(( x) { x.Price += increment; });




articles.Sort(( x, y){
<>.Default.Compare(x.Price,y.Price); });


System.Converter(T from)
<> articlesPrices = articles.ConvertAll<>(
( article) { ()article.Price; });


System..ReadKey();
}
}

new, Courier, mono">System.Collections.Generic.List and the enhanced new, Courier, mono">System.Array classes are the only classes of the .NET framework that take benefits of functors. Here is the exhaustive list of methods that support functors:

public class  : System.Collections.Generic.,
System.Collections.Generic.,
System.Collections.Generic., System.Collections.,
System.Collections., System.Collections.{
FindIndex(Predicate match);
FindIndex( index, Predicate match);
FindIndex( index, count, Predicate match);

FindLastIndex(Predicate match);
FindLastIndex( index, Predicate match);
FindLastIndex( index, count, Predicate match);

List FindAll(Predicate match);
T Find(Predicate match);
T FindLast(Predicate match);

Exists(Predicate match);
TrueForAll(Predicate match);

RemoveAll(Predicate match);
ForEach(Action action);
Sort(Comparison Comparison);
public List ConvertAll(Converter converter);
...
}
public class {
FindIndex(T[] array, startIndex,
count, Predicate match);
FindIndex(T[] array, startIndex,
Predicate match);
FindIndex(T[] array, Predicate match);

FindLastIndex(T[] array, startIndex,
count, Predicate match);
FindLastIndex(T[] array, startIndex,
Predicate match);
FindLastIndex(T[] array, Predicate match);

T[] FindAll(T[] array, Predicate match);
T Find(T[] array, Predicate match);
T FindLast(T[] array, Predicate match);

Exists(T[] array, Predicate match);
TrueForAll(T[] array, Predicate match);

ForEach(T[] array, Action action);
Sort(T[] array, System. comparison);
U[] ConvertAll(T[] array, Converter converter);
...
}

Conclusion

After going through the basics of anonymous methods, we discovered that this feature is more complex and more useful than expected at first glance. This new C# v2 feature is an implementation of the notion of closures of functional languages. In C# v2, closures can be seen as some syntactic sugar and understanding the work of the compiler is essential. We saw that using closures incorrectly can be harmful for code clarity. We saw that closures are a good means to replace some small classes. They can also dramatically enhance list manipulation.

In a next article we'll cover iterators of C# v2. We'll underline the work of the compiler in order to use properly this feature.

References:

The C# programming language by Anders Hejlsberg, Scott Wiltamuth, Peter Golde
Create Elegant Code with Anonymous Methods, Iterators, and Partial Classes by Juval Lowy
Implementation of Closures (Anonymous Methods) in C# 2.0 (Part 6) Roshan James's blog
Closures in CLR 2.0 Antonio Cisternino's blogs
Fun with Anonymous Methods Brad Abrams's blog
What is closure c2 Wiki
Anonymous Methods c2 Wiki
Anonymous Methods, Part 2 of ? GrantRi's WebLog [MS]
Charming Python: Functional programming in Python, Part 2 by David Mertz

Authors

Patrick Smacchia is a .NET MVP involved in software development for over 15 years. After graduating in mathematics and computer science from the ENSEEIHT school, he has worked on software in a variety of fields including stock exchange at Société Générale, airline ticket reservation system at Amadeus as well as a satellite base station at Alcatel. He's currently a software consultant and trainer on .NET technologies as well as the author of the freeware NDepend which provides numerous metrics and caveats on any compiled .NET application.
 
  

你可能感兴趣的:(C#)