views:

187

answers:

2

I am curious about the actual .NET implementation and the decision behind it.

For example in Java, all captured values used in an anonymous classes are required to be final. This requirement seems to be dropped in .NET.

Also, is there a difference in an implementation of captured values for value types as opposed to reference types?

Thanks

+5  A: 

The easiest way of finding out how it's implemented is to try it. Write some code which uses a captured variable, compile it, then look at it in Reflector. Note that it's the variable which is captured, not the value. That's one of the big differences between Java and C# in this area.

The basic idea is that each level of scope containing at least one captured variable results in a new class with fields for the variables which have been captured. If there's more than one level, then an inner scope also has a field for the next scope out, and so on. The genuine local variables on the stack end up being references to instances of the autogenerated classes.

Here's an example:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        for (int i=0; i < 5; i++)
        {
            int copyOfI = i;

            for (int j=0; j < 5; j++)
            {
                int copyOfJ = j;

                actions.Add(delegate
                {
                    Console.WriteLine("{0} {1}", copyOfI, copyOfJ);
                });
            }
        }

        foreach (Action action in actions)
        {
            action();
        }        
    }
}

(You get different results if you don't take a copy of course - experiment!) This is compiled into code like this:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        for (int i=0; i < 5; i++)
        {
            OuterScope outer = new OuterScope();
            outer.copyOfI = i;

            for (int j=0; j < 5; j++)
            {
                InnerScope inner = new InnerScope();
                inner.outer = outer;
                inner.copyOfJ = j;

                actions.Add(inner.Action);
            }
        }

        foreach (Action action in actions)
        {
            action();
        }        
    }

    class OuterScope
    {
        public int copyOfI;
    }

    class InnerScope
    {
        public int copyOfJ;
        public OuterScope outer;

        public void Action()
        {
            Console.WriteLine("{0} {1}", outer.copyOfI, copyOfJ);
        }
    }
}

Every reference to the captured variable ends up going through the instance of the generated class, so it's not just a one-off copy. (Okay, in this case nothing else in the code uses the captured variables, but you can easily imagine it could.) Note how for any one iteration of the outer loop, the five new instances all share one instance of OuterScope. You might want to try playing with extra code in the delegate to see how that affects things - if the delegate changes copyofI that change will be seen in the next delegate; changes to copyOfJ won't be seen because the next delegate will be using a separate instance of InnerScope.

Jon Skeet
Hi, very nice explanation. You could also show what the compiler would produce without the copying to local variable.
Martin Konicek