Dec 232009
 

Anonymous methods are a convenient way of using delegates in .NET 2.0 and above. For e.g. if we use a delegate for a simple 2-3 line functionality, declaring a separate method and then passing the target to the delegate seems like overkill. Anonymous methods provide an easy way out, just declare the body of the method after the delegates declaration and you have no need to write a seperate method.

Anonymous methods are a compiler feature. Hence, from the CLR’s point of view there is nothing anonymous about it. The C# compiler generates a method with your delegate declaration and adds it to the invocation list for the delegate. Here is some sample code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 class TestClass
    {
        delegate void writeToScreen(string _inpar);
 
        public static void TestDelegates()
        {
            //1.1 way of doing things. Notice the seperate method.
            writeToScreen _delObj = new writeToScreen(new TestClass().NamedMethod);
 
            //2.0 way of calling it
            _delObj += delegate(string _inpar)
            {
                Console.WriteLine("Anonymous method says: "+_inpar);
            };
 
            //Lets invoke the delegate
            _delObj.Invoke("Hello World");
        }
 
        public void NamedMethod(string _inpar)
        {
            Console.WriteLine("Named method says: " + _inpar);
        }
    }

In the above code, the 1.1 way would have you scrambling to find the definition of the NamedMethod, but in second delegate its simpler and far more easier to understand. In the IL, the compiler generates a method for the anonymous block of code that you write.

1
2
3
4
5
6
7
.method private hidebysig static void  'b__0'(string _inpar) cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
//removed for brevity
  IL_0010:  ret
}
// end of method TestClass::'b__0'

So far so good, but anonymous methods bring more questions to the table. How are variables in the outer scope treated? Can they be accessed? Yes they can and they have a fancy name as well – Captured variables. Here is some code for it.

1
2
3
4
5
6
7
8
9
10
11
12
13
            int _localVar = 0;
            _delObj += delegate(string _inpar)
            {
                _localVar++;
                Console.WriteLine("Anonymous method says: "+_inpar);
            };
 
            //Before invoking the delegate the value is 0
            Console.WriteLine("Local Variable : " + _localVar);
            //Lets invoke the delegate
            _delObj.Invoke("Hello World");
            //After invoking, the value becomes 1
            Console.WriteLine("Local Variable : " + _localVar);

Behind the screens the compilers does a lot of work to get captured variables work. In the MSIL, there is no sign of the local variable, instead a reference variable is created which has the local integer as a member and all modifications are done to it.

The IL shows the int declaration has disappeared in the TestDelegates method. Instead an instance of the class c_Displayclass1 is created and used. The local variable is a member of this instance class and this is the variable that is incremented both in the TestDelegates method as well as the invocation giving the illusion of usage of the local variable in the anonymous method body scope. For short declarations and one or two variables this feature would be quite useful, but a large number of captured variables would make the code difficult to understand, hence compromising the biggest advantage of anonymous methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.method public hidebysig static void  TestDelegates() cil managed
{
   .locals init ([0] class AnonyMeth.TestClass/writeToScreen _delObj,
           [1] class AnonyMeth.TestClass/'<>c__DisplayClass1' 'CS$<>8__locals2')
  IL_0000:  newobj     instance void AnonyMeth.TestClass/'<>c__DisplayClass1'::.ctor()
  IL_0005:  stloc.1
 //removed for brevity
  IL_0019:  stfld      int32 AnonyMeth.TestClass/'<>c__DisplayClass1'::_localVar
  IL_001e:  ldloc.0
  IL_001f:  ldloc.1
  IL_0020:  ldftn      instance void AnonyMeth.TestClass/'<>c__DisplayClass1'::'b__0'(string)
  IL_0026:  newobj     instance void AnonyMeth.TestClass/writeToScreen::.ctor(object,
                                                                              native int)
  IL_002b:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
                                                                                          class [mscorlib]System.Delegate)
  IL_0030:  castclass  AnonyMeth.TestClass/writeToScreen
  IL_0035:  stloc.0
  IL_0036:  ldstr      "Local Variable : "
  IL_003b:  ldloc.1
  IL_003c:  ldfld      int32 AnonyMeth.TestClass/'<>c__DisplayClass1'::_localVar
  //The instance variable of c__displayclass1 is displayed here
  //removed for brevity
  IL_0075:  ret
} // end of method TestClass::TestDelegates