views:

334

answers:

4

In the following program, DummyMethod always print 5. But if we use the commented code instead, we get different values (i.e. 1, 2, 3, 4). Can anybody please explain why this is happenning?

        delegate int Methodx(object obj);

        static int DummyMethod(int i)
        {
            Console.WriteLine("In DummyMethod method i = " + i);
            return i + 10;
        }

        static void Main(string[] args)
        {    
            List<Methodx> methods = new List<Methodx>();

            for (int i = 0; i < 5; ++i)
            {
                methods.Add(delegate(object obj) { return DummyMethod(i); });
            }

            //methods.Add(delegate(object obj) { return DummyMethod(1); });
            //methods.Add(delegate(object obj) { return DummyMethod(2); });
            //methods.Add(delegate(object obj) { return DummyMethod(3); });
            //methods.Add(delegate(object obj) { return DummyMethod(4); });

            foreach (var method in methods)
            {
                int c = method(null);
                Console.WriteLine("In main method c = " + c);
            }
        }

Also if the following code is used, I get the desired result.

        for (int i = 0; i < 5; ++i)
        {
            int j = i;
            methods.Add(delegate(object obj) { return DummyMethod(j); });
        }
+17  A: 

The problem is that you're capturing the same variable i in every delegate - which by the end of the loop just has the value 5.

Instead, you want each delegate to capture a different variable, which means declaring a new variable in the loop:

for (int i = 0; i < 5; ++i)
{
    int localCopy = i;
    methods.Add(delegate(object obj) { return DummyMethod(localCopy); });
}

This is a pretty common "gotcha" - you can read a bit more about captured variables and closures in my closures article.

Jon Skeet
Jon, trying to get my head around why this happens. I looked at "i" as a value-type thinking "thats got to be passed as a copy", so I couldn't see how it seemed to have reference-like behaviour... You got a summary / one-liner to point me in right direction?
Neil Fenwick
Have a look at the final link. The thing to get your head round is that it's not the variable's *value* which is captured - it's the variable itself.
Jon Skeet
Think about this: It's like there's a little inline event that's called right in that spot in the method and thus has access to all of the variables and state, only you get the variables and state when the method's called not created. Thus you're simply accessing i when the method is called not when you make the anonymous method.
RCIX
@Neil I've put the "behind-the-scenes" code in my answer below. This is what helped me understand all this.
Matt Warren
+4  A: 

This article will probably help you understand what is happening (i.e. what a closure is): http://blogs.msdn.com/oldnewthing/archive/2006/08/02/686456.aspx

danbystrom
+2  A: 

I think it is because the variable i is put to the heap (it's a captured variable)

Take a look at this answer.

Stefan Steinegger
I think "captured variable" is more useful terminology - precisely because it's the *variable* which is captured rather than its *value*.
Jon Skeet
@Jon: Sounds reasonable, I fix it
Stefan Steinegger
+4  A: 

If you look at the code generated (using Reflector) you can see the difference:

private static void Method2()
{
    List<Methodx> list = new List<Methodx>();
    Methodx item = null;
    <>c__DisplayClassa classa = new <>c__DisplayClassa();
    classa.i = 0;
    while (classa.i < 5)
    {
        if (item == null)
        {
            item = new Methodx(classa.<Method2>b__8);
        }
        list.Add(item);
        classa.i++;
    }
    foreach (Methodx methodx2 in list)
    {
        Console.WriteLine("In main method c = " + methodx2(null));
    }
}

When you use the initial code it creates a temporary class in the background, this class holds a reference to the "i" variable, so as per Jon's answer, you only see the final value of this.

private sealed class <>c__DisplayClassa
{
    // Fields
    public int i;

    // Methods
    public <>c__DisplayClassa();
    public int <Method2>b__8(object obj);
}

I really recommend looking at the code in Reflector to see what's going on, its how I made sense of captured variables. Make sure you set the Optimization of the code to ".NET 1.0" in the Option menu, otherwise it'll hide all the behind scenes stuff.

Matt Warren