The following article is excerpted from the chapter 14 of the book Practical .NET2 and C#2.
Contents
This article presents a new feature named anonymous methods added to the 2.0 version of the C# language. Unlike generics, this feature does not involve new IL instructions. All the magic happens at the level of the compiler.
Let's begin by enhancing some C#1 code to use C#2 anonymous methods. Here is a simple C# v1 program that first references and then invokes a method, through a delegate:
Example1.cs
class Program {
delegate void DelegateType();
static DelegateType GetMethod(){
return new DelegateType(MethodBody);
}
static void MethodBody() {
System.Console.WriteLine("Hello");
}
static void Main() {
DelegateType delegateInstance = GetMethod();
delegateInstance();
delegateInstance();
}
}
Here is the same program rewritten using a C#2 anonymous method:
Example2.cs
class Program {
delegate void DelegateType();
static DelegateType GetMethod() {
return delegate() { System.Console.WriteLine("Hello"); };
}
static void Main() {
DelegateType delegateInstance = GetMethod();
delegateInstance();
delegateInstance();
}
}
You should notice that:
- The
delegate
keyword has a new use in C#2. When the C#2 compiler finds the delegate
keyword inside the body of a class, it expects it to be 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):
Example3.cs
using System;
class Program{
delegate void DelegateType();
static void Main(){
DelegateType delegateInstance = delegate() {
Console.WriteLine("Hello"); };
delegateInstance += delegate() { Console.WriteLine("Bonjour"); };
delegateInstance();
}
}
As you might expect, this program outputs:
Hello
Bonjour
As shown in the following example, an anonymous method can accept arguments of any type. You can also use the keywords ref
and out
to tune how arguments are passed to the method:
Example4.cs
class Program {
delegate int DelegateType( int valTypeParam, string refTypeParam,
ref int refParam, out int outParam);
static DelegateType GetMethod() {
return delegate( int valTypeParam , string refTypeParam,
ref int refParam , out int outParam ) {
System.Console.WriteLine( "Hello valParam:{0} refTypeParam:{1}",
valTypeParam, refTypeParam);
refParam++;
outParam = 9;
return valTypeParam;
};
}
static void Main() {
DelegateType delegateInstance = GetMethod();
int refVar = 5;
int outVar;
int i = delegateInstance( 1, "one", ref refVar, out outVar );
int j = delegateInstance( 2, "two", ref refVar, out outVar );
System.Console.WriteLine( "i:{0} j:{1} refVar:{2} outVar:{3}",
i, j, refVar, outVar);
}
}
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 the 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.
Example5.cs
using System;
class Program {
delegate void DelegateType( params int[] arr );
static DelegateType GetMethod() {
return delegate( params int[] arr ){ Console.WriteLine("Hello");};
}
}
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 out
arguments. Obviously, such an anonymous method doesn't have access to the parameters that are provided through its delegate invocation.
Example6.cs
using System;
class Program{
delegate void DelegateType(int valTypeParam, string refTypeParam,
ref int refParam);
static void Main() {
DelegateType delegateInstance = delegate {
Console.WriteLine( "Hello" ); };
int refVar = 5;
delegateInstance( 1, "one", ref refVar );
delegateInstance( 2, "two", ref refVar );
}
}
As shown in the example below, an argument of an anonymous method can be of a generic type:
Example7.cs
class Foo<T> {
delegate void DelegateType( T t );
internal void Fct( T t ) {
DelegateType delegateInstance = delegate( T arg ){
System.Console.WriteLine( "Hello arg:{0}" , arg.ToString() ); };
delegateInstance( t );
}
}
class Program {
static void Main() {
Foo<DOUBLE> inst = new Foo <DOUBLE>();
inst.Fct(5.5);
}
}
In .NET 2, a delegate type can be declared with generic arguments. An anonymous method can be assigned to a delegate instance of such a type. You just have to resolve type parameters on both sides of the assignment:
Example8.cs
class Program{
delegate void DelegateType<T>( T t );
static void Main() {
DelegateType<double> delegateInstance = delegate( double arg ) {
System.Console.WriteLine( "Hello arg:{0}" , arg.ToString() );
};
delegateInstance(5.5);
}
}
Anonymous methods are particularly suited to define ‘small’ methods that must be invoked through a delegate. For instance, you might use an anonymous method to code the entry point procedure of a thread:
Example9.cs
using System.Threading;
class Program{
static void Main(){
Thread thread = new Thread( delegate() {
System.Console.WriteLine( "ManagedThreadId:{0} Hello",
Thread.CurrentThread.ManagedThreadId );
} );
thread.Start();
System.Console.WriteLine( "ManagedThreadId:{0} Bonjour",
Thread.CurrentThread.ManagedThreadId );
}
}
This program displays:
ManagedThreadId:1 Bonjour
ManagedThreadId:3 Hello
Another classic example of this kind of use lies in the Windows Forms control event callbacks:
Example10.cs
public class FooForm : System.Windows.Forms.Form {
System.Windows.Forms.Button m_Button;
public FooForm() {
InitializeComponent();
m_Button.Click += delegate( object sender, System.EventArgs args ) {
System.Windows.Forms.MessageBox.Show("m_Button Clicked");
};
}
void InitializeComponent() {}
}
It seems that an 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.
As you might expect, when an anonymous method is compiled, a new method is created by the compiler in the concerned class:
Example11.cs
class Program {
delegate void DelegateType();
static void Main() {
DelegateType delegateInstance = delegate() {
System.Console.WriteLine("Hello"); };
delegateInstance();
}
}
The following assembly is the compiled version of the previous program (viewed using the Reflector tool):
Indeed, a new private
and static
method named <Main>b__0()
has been automatically generated, and contains the code for 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 IL/CLR syntax but incorrect for the C# syntax.
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 feature through the following example:
Example12.cs
class Program {
delegate int DelegateTypeCounter();
static DelegateTypeCounter MakeCounter(){
int counter = 0;
DelegateTypeCounter delegateInstanceCounter =
delegate { return ++counter; };
return delegateInstanceCounter;
}
static void Main() {
DelegateTypeCounter counter1 = MakeCounter();
DelegateTypeCounter counter2 = MakeCounter();
System.Console.WriteLine( counter1() );
System.Console.WriteLine( counter1() );
System.Console.WriteLine( counter2() );
System.Console.WriteLine( counter2() );
}
}
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 MakeCounter()
method. Moreover, it seems that two instances of this 'surviving' local variable exist!
Note that in .NET 2, the CLR and the IL language haven't been tweaked to support the anonymous method feature. The interesting behavior must stem from the compiler. It's a nice example of syntactic sugar. Let's analyze 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
<MakeCounter>b__0()
. This method has the body of our anonymous method.
- This class also has 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 MakeCounter()
method doesn't have any local variable. For the counter
variable, it uses the same field of the generated instance of the class <>c__DisplayClass1
.
Before explaining why the compiler has this surprising behavior, let's go further to get a thorough understanding of its work.
The following example is more subtle than expected:
Example13.cs
using System.Threading;
class Program {
static void Main() {
for (int i = 0; i < 5; i++)
ThreadPool.QueueUserWorkItem( delegate {
System.Console.WriteLine(i); }, null);
}
}
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 (or anonymous methods) are executed simultaneously by several threads. To make things clear, here is the decompiled code of the Main()
method:
private static void Main(){
bool flag1;
Program.<>c__DisplayClass1 class1 = new Program.<>c__DisplayClass1();
class1.i = 0;
goto Label_0030;
Label_000F:
ThreadPool.QueueUserWorkItem(new WaitCallback(class1.<Main>b__0), null);
class1.i++;
Label_0030:
flag1 = class1.i < 5;
if ( flag1 ) {
goto Label_000F;
}
}
Notice that the fact that the value of 5 being printed indicates that the Main()
method is done executing the loop when the display is done. The following version of this program has a deterministic execution:
Example14.cs
using System.Threading;
class Program {
static void Main() {
for (int i = 0; i < 5; i++){
int j = i;
ThreadPool.QueueUserWorkItem(delegate {
System.Console.WriteLine(j); }, null);
}
}
}
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:
private static void Main(){
Program.<>c__DisplayClass1 class1;
bool flag1;
int num1 = 0;
goto Label_0029;
Label_0004:
class1 = new Program.<>c__DisplayClass1();
class1.j = num1;
ThreadPool.QueueUserWorkItem(new WaitCallback(class1.<Main>b__0), null);
num1++;
Label_0029:
flag1 = num1 < 5;
if (flag1) {
goto Label_0004;
}
}
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 pinned it before (with the C# keyword fixed
).
Arguments of a method can always be deemed as local variables. Therefore, C#2 allows an anonymous method to use arguments of its outer method. For instance:
Example15.cs
using System;
class Program {
delegate void DelegateTypeCounter();
static DelegateTypeCounter MakeCounter( string counterName ) {
int counter = 0;
DelegateTypeCounter delegateInstanceCounter = delegate{
Console.WriteLine( counterName + (++counter).ToString() );
};
return delegateInstanceCounter;
}
static void Main() {
DelegateTypeCounter counterA = MakeCounter("Counter A:");
DelegateTypeCounter counterB = MakeCounter("Counter B:");
counterA();
counterA();
counterB();
counterB();
}
}
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 easy to understand 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.
An anonymous method can access members of its outer class. The case of static member access is easy to understand 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.
The access to the instance of a member 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:
Example16.cs
delegate void DelegateTypeCounter();
class CounterBuilder {
string m_Name;
internal CounterBuilder( string name ) { m_Name = name; }
internal DelegateTypeCounter BuildCounter( string counterName ) {
int counter = 0;
DelegateTypeCounter delegateInstanceCounter = delegate {
System.Console.Write( counterName +(++counter).ToString() );
System.Console.WriteLine(" Counter built by: " + m_Name);
};
return delegateInstanceCounter;
}
}
class Program {
static void Main() {
CounterBuilder cBuilder1 = new CounterBuilder( "Factory1" );
CounterBuilder cBuilder2 = new CounterBuilder( "Factory2" );
DelegateTypeCounter cA = cBuilder1.BuildCounter( "Counter A:" );
DelegateTypeCounter cB = cBuilder1.BuildCounter( "Counter B:" );
DelegateTypeCounter cC = cBuilder2.BuildCounter( "Counter C:" );
cA(); cA ();
cB(); cB();
cC(); cC();
}
}
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 capture of the this
reference:
internal DelegateTypeCounter BuildCounter(string counterName){
CounterBuilder.<>c__DisplayClass1 class1 = new
CounterBuilder.<>c__DisplayClass1();
class1.<>4__this = this;
class1.counterName = counterName;
class1.counter = 0;
return new DelegateTypeCounter(class1.<BuildCounter>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: Anonymous methods inside structs cannot access instance members of ‘this
’. Consider copying ‘this
’ to a local variable outside the anonymous method and using the local instead.
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 carefully used 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 also indicates that the notion of lexical environment pinpoints to 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#2 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 a 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#2 goes beyond imperative languages by supporting closures. However, C#2 is not the first imperative language that supports closures, since Perl and Ruby also have this feature.
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 the global variables. When dealing with closures, the context is the values of the captured variables when the closure is created. Therefore, as classes, closures are a way to associate behavior and data. In the object oriented world, methods and data are associated, thanks to the this
reference. In the functional world, a function is associated with the values of captured variables. To make things clear:
- You can think of an object as a set of methods attached to a set of data.
- You can think of a closure as a set of data attached to a function.
The previous section implies that some type of classes could be replaced by some anonymous methods. Actually, we already perform such replacements in our implementation of the counter. The behavior is the increment of the counter, while the state is its value. However, the counter implementation doesn't harness the possibility of passing arguments to an anonymous method. The following example shows how to harness closures to perform parameterized computation on the state of an object:
Example17.cs
class Program {
delegate void DelegateMultiplier( ref int integerToMultipl);
static DelegateMultiplier BuildMultiplier ( int multiplierParam ) {
return delegate( ref int integerToMultiply ) {
integerToMultiply *= multiplierParam;
};
}
static void Main() {
DelegateMultiplier multiplierBy8 = BuildMultiplier(8);
DelegateMultiplier multiplierBy2 = BuildMultiplier(2);
int anInteger = 3;
multiplierBy8( ref anInteger );
multiplierBy2( ref anInteger );
}
}
Here is another example that shows how to harness closures to perform parameterized computation in order to obtain a value from the state of an object:
Example18.cs
using System;
class Article {
public Article( decimal price ) { m_Price = price; }
private decimal m_Price;
public decimal Price { get { return m_Price; } }
}
class Program {
delegate decimal DelegateTaxComputer( Article article );
static DelegateTaxComputer BuildTaxComputer( decimal tax ) {
return delegate( Article article ) {
return ( article.Price * (100 + tax) ) / 100;
};
}
static void Main(){
DelegateTaxComputer taxComputer19_6 = BuildTaxComputer(19.6m);
DelegateTaxComputer taxComputer5_5 = BuildTaxComputer(5.5m);
Article article = new Article(97);
Console.WriteLine("Price TAX 19.6% : " + taxComputer19_6(article) );
Console.WriteLine("Price TAX 5.5% : " + taxComputer5_5(article) );
}
}
Understand that all the power behind the use of closures in both previous examples comes from the fact that they prevent us from creating small classes (which are in fact created implicitly by the compiler).
By taking a closer look, we notice that the notion of a delegate used on an instance method in .NET 1.x is conceptually close to the notion of closure. In fact, such a delegate references both data (the state of the object) and a behavior. A constraint does exist: the behavior must be an instance method of the class defining the type of the this
reference.
This constraint is minimized in .NET 2. Because of certain overloads of the Delegate.CreateDelegate()
method, you can now reference the first argument of a static method in a delegate. For example:
Example19.cs
class Program {
delegate void DelegateType( int writeNTime );
public static void WriteLineNTimes( string s, int nTime ) {
for( int i=0; i < nTime; i++ )
System.Console.WriteLine( s );
}
static void Main() {
DelegateType deleg = System.Delegate.CreateDelegate(
typeof( DelegateType ),
"Hello",
typeof(Program).GetMethod( "WriteLineNTimes" )) as DelegateType;
deleg(4);
}
}
This program displays:
Hello
Hello
Hello
Hello
Note that internally, the implementation of delegates has been completely revised in the 2.0 version of the framework and the CLR. The good news is that the invocation of a method through a delegate is now much more efficient.
The System
namespace contains four new delegate types, particularly useful to manipulate and obtain information from collections:
namespace System {
public delegate void Action<T> ( T obj );
public delegate bool Predicate<T> ( T obj );
public delegate U Converter<T,U> ( T from );
public delegate int Comparison<T> ( T x, T y );
}
The following example exposes four processes which can be done on a list (a request, a calculation, a sort, and a conversion), done using instances of these delegates:
Example20.cs
using System.Collections.Generic;
class Program {
class Article {
public Article(decimal price,string name){Price=price;Name=name;}
public readonly decimal Price;
public readonly string Name;
}
static bool IsEven(int i) { return i % 2 == 0; }
static int sum = 0;
static void AddToSum(int i) { sum += i; }
static int CompareArticle(Article x, Article y){
return Comparer<DECIMAL>.Default.Compare(x.Price, y.Price);
}
static decimal ConvertArticle(Article article){
return (decimal)article.Price;
}
static void Main(){
List<INT> integers = new List<INT>();
for(int i=1; i<=10; i++) {
integers.Add(i);
}
List<INT> even = integers.FindAll( IsEven );
integers.ForEach( AddToSum );
List<ARTICLE> articles = new List<ARTICLE>();
articles.Add( new Article(5,"Shoes") );
articles.Add( new Article(3,"Shirt") );
articles.Sort( CompareArticle );
List<DECIMAL> artPrice = articles.ConvertAll<DECIMAL>( ConvertArticle );
}
}
Readers who have used the Standard Template Library (STL) of C++ will recognize the notion of a functor. A functor is a parameterized process. Functors are particularly useful to complete the same operation on all the elements of a collection. In C++, we overloaded the parenthesis operator in order to implement the notion of a functor. In .NET, a functor takes the form of a delegate instance. In fact, in the previous program, the four delegate instances created implicitly are four examples of functors.
As shown in the following example, the anonymous methods of C# can prove themselves to be particularly adapted to the implementation of functors. Note that, as with the second functor, which stores the sum of the elements in an integer, a functor can encapsulate a state.
Example21.cs
using System.Collections.Generic;
class Program {
class Article {
public Article(decimal price,string name){Price=price;Name=name;}
public readonly decimal Price;
public readonly string Name;
}
static void Main(){
List<INT> integers = new List<INT>();
for(int i=1; i<=10; i++) {
integers.Add(i);
}
List<INT> even =integers.FindAll( delegate(int i){ return i%2==0; });
int sum = 0;
integers.ForEach(delegate(int i) { sum += i; });
List<ARTICLE> articles = new List<ARTICLE>();
articles.Add( new Article(5,"Shoes") );
articles.Add( new Article(3,"Shirt") );
articles.Sort(delegate(Article x, Article y) {
return Comparer<DECIMAL>.Default.Compare(x.Price,y.Price); } );
List<DECIMAL> artPrice = articles.ConvertAll<DECIMAL> (
delegate(Article article) { return (decimal)article.Price; } );
}
}
The use of functors is only possible on collections of type List<T>
and Array
. In fact, only these collections offer methods which accept functors to process their elements. These methods, which have self-descriptive names, are listed below:
public class List<T> : ... {
public int FindIndex(Predicate<T> match);
public int FindIndex(int index, Predicate<T> match);
public int FindIndex(int index, int count, Predicate<T> match);
public int FindLastIndex(Predicate<T> match);
public int FindLastIndex(int index, Predicate<T> match);
public int FindLastIndex(int index, int count, Predicate<T> match);
public List<T> FindAll(Predicate<T> match);
public T Find(Predicate<T> match);
public T FindLast(Predicate match);
public bool Exists(Predicate<T> match);
public bool TrueForAll(Predicate<T> match);
public int RemoveAll(Predicate<T> match);
public void ForEach(Action<T> action);
public void Sort(Comparison<T> comparison);
public List<U> ConvertAll<U>(Converter<T,U> converter);
...
}
public class Array {
public static int FindIndex<T>(T[] array, int startIndex,
int count, Predicate<T> match);
public static int FindIndex<T>(T[] array, int startIndex,
Predicate<T> match);
public static int FindIndex<T>(T[] array, Predicate<T> match);
public static int FindLastIndex<T>(T[] array, int startIndex,
int count, Predicate<T> match);
public static int FindLastIndex<T>(T[] array, int startIndex,
Predicate<T> match);
public static int FindLastIndex<T>(T[] array, Predicate<T> match);
public static T[] FindAll<T>(T[] array, Predicate<T> match);
public static T Find<T>(T[] array, Predicate<T> match);
public static T FindLast<T>(T[] array, Predicate<T> match);
public static bool Exists<T>(T[] array, Predicate<T> match);
public static bool TrueForAll<T>(T[] array, Predicate<T> match);
public static void ForEach<T>(T[] array, Action<T> action);
public static void Sort<T>(T[] array, System.Comparison<T> comparison);
public static U[] ConvertAll<T, U>( T[] array,
Converter<T, U> converter);
...
}