|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
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. Introduction to C#2 anonymous methodsLet'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: 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: 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:
You should notice as well that it is possible to use the operator 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
Anonymous methods can accept argumentsAs shown in the following example, an anonymous method can accept arguments of any type. You can also use the keywords 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;
}; // End of the body of the anonymous method.
}
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 using System;
class Program {
delegate void DelegateType( params int[] arr );
static DelegateType GetMethod() {
// Compilation error: param is not valid in this context.
return delegate( params int[] arr ){ Console.WriteLine("Hello");};
}
}
A syntax subtletyIt 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 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 );
}
}
Anonymous methods and genericsAs shown in the example below, an argument of an anonymous method can be of a generic type: 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: 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);
}
}
Use of anonymous methods in the real worldAnonymous 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: 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: 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. The C#2 compiler and anonymous methodsThe easy wayAs you might expect, when an anonymous method is compiled, a new method is created by the compiler in the concerned class: 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 We also note that a delegate field named 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. Captured local variableTo 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: 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 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:
Notice that the Before explaining why the compiler has this surprising behavior, let's go further to get a thorough understanding of its work. Captured local variables and code complexityThe following example is more subtle than expected: 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 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 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 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 An anonymous method accessing an argument of the outer methodArguments 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: 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 An anonymous method accessing a member of the outer classAn 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 delegate void DelegateTypeCounter();
class CounterBuilder {
string m_Name; // Un champ d’instance
internal CounterBuilder( string name ) { m_Name = name; }
internal DelegateTypeCounter BuildCounter( string counterName ) {
int counter = 0;
DelegateTypeCounter delegateInstanceCounter = delegate {
System.Console.Write( counterName +(++counter).ToString() );
// On aurait pu écrire this.m_Name.
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 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 Anonymous methods and closuresDefinitions: closure and lexical environmentA 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. Ramblings on closuresA 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
Using closures instead of classesThe 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: 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 );
// Here, anInteger is equal to 24.
multiplierBy2( ref anInteger );
// Here, anInteger is equal to 48.
}
}
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: 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). Delegates and closuresBy 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 constraint is minimized in .NET 2. Because of certain overloads of the class Program {
delegate void DelegateType( int writeNTime );
// This method is public to avoid problems of reflection
// on a non-public member.
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. Anonymous methods and functorsIntroduction to functorsThe 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: 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(){
// Seek out every odd integers.
// Implicitly uses a ‘Predicate<T>’ delegate object.
List
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. Using anonymous methods and functors to query collectionsAs 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. 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(){
// Seek out every odd integers.
// Implicitly uses a ‘Predicate<T>’ delegate object.
List
List
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|