Anonymous Delegates: Under the Hood

time to read 27 min | 5298 words

How does C# anonymous delegates works? Like most things in computers*, it's a big fat lie.

Let's assume that I have this code:

static void Main (string[] args)

{

    List<Action<string>> actions = new List<Action<string>>();

    int[] arr = { 1, 2, 3, 4, 5 };

 

    foreach (int i in arr)

    {

        actions.Add(delegate { Console.WriteLine(i); });

    }

 

    foreach (Action<string> action in actions)

    {

        action(null);

    }

}

I write such code fairly typically, but I know that some people has problems understanding this code, so let's see what really** is going on there. It's entirely possible to use anonymous methods on 1.1, there is nothing in the framework that needed to be changed, it is merely a compiler trick. Looking at the code using Reflector, we see that the Main method was transfromed into:

private static void Main (string[] args)

{

      List<Action<string>> list1 = new List<Action<string>>();

      int[] numArray1 = new int[] { 1, 2, 3, 4, 5 };

      Action<string> action1 = null;

      Program.<>c__DisplayClass2 class1 = new Program.<>c__DisplayClass2();

      int[] numArray2 = numArray1;

      for (int num1 = 0; num1 < numArray2.Length; num1++)

      {

            class1.i = numArray2[num1];

            if (action1 == null)

            {

                  action1 = new Action<string>(class1.<Main >b__0);

            }

            list1.Add((Action<string>) action1);

      }

      foreach (Action<string> action2 in list1)

      {

            action2(null);

      }

}

 

[CompilerGenerated]

private sealed class <>c__DisplayClass2

{

      public void <Main >b__0(string)

      {

              Console.WriteLine(this.i);

      }

      public int i;

}

By the way, the <> prefix you see on the class name? This is just a way to make sure that there are no name clashes. The CLR supports those names, but no language is allowed to make them visible to developers, they are strictly for internal use only.

I'm really bitter about this compiler reserved symbols, because I really wanted to write code like this one:

<XmlDocument> <xml> = new <XmlDocument>();
<xml>.<AddElement>(new <Element>());

This would have been possible if only I could have the "<" and ">" symbols as part of my programming langague. After all, it's not like I have enough angle brackets in my daily development as it is :-)

 

Anyway, you can see that the compiler generated class actually has the code that I had in the anonymous method, and that the local variable that I referenced if a public field on that class. You can also see that I got a variable named "class1" that is created before we enter the loop, and on each iteration through the loop, we set the "class1.i" to the current indexer.

That is really the problem. The scope of the loop indexer is the scope of the method, and so the class1 is only instansiated once. In essense, we filled the list with 5 references to the same objects. No wonder that we got the results 5 five times.

To fix that, we need to add a temporary varaible that is in the scope of the for, like this:

static void Main (string[] args)

{

    List<Action<string>> actions = new List<Action<string>>();

    int[] arr = { 1, 2, 3, 4, 5 };

 

    foreach (int i in arr)

    {

        int x = i;

        actions.Add(delegate { Console.WriteLine(x); });

    }

 

    foreach (Action<string> action in actions)

    {

        action(null);

    }

}

This code prodcues the expected result, and it's actually no surprise at all, check out the code that is being generated for this one:

 

private static void Main (string[] args)

{

      List<Action<string>> list1 = new List<Action<string>>();

      int[] numArray1 = new int[] { 1, 2, 3, 4, 5 };

      foreach (int num1 in numArray1)

      {

            Program.<>c__DisplayClass1 class1 = new Program.<>c__DisplayClass1();

            class1.x = num1;

            list1.Add((Action<string>) new Action<string>(class1.<Main >b__0));

      }

      foreach (Action<string> action1 in list1)

      {

            action1(null);

      }

}

Now we create a new instance each time we goes through the loop, and we get the correct result. As you can see, there is nothing special about anonymous methods, it's just the compiler being tricky and conniving as usual. In this case, I was caught by the gotcha, but all you need to remember is that anonymous delegates are created in the scope of their inner most varaible. This can be very important for this kind of scenarios, where you got a list of actions to take.

* Memory, networking, abstractions, bytes, etc.

** Well, not really, I'm not going to show you how a long streams of 01000101010100 is turned into a bug, but it's close enough.