Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why this C# program outputs such a result? How do I understand closure?

Tags:

closures

c#

.net

I was trying to understand the answer for this question Why am I getting wrong results when calling Func<int>? I wrote some sample code. The following code

public static void Main(string[] args)
{
    var funcs = new List<Func<string>>();
    for(int v=0,i=0;v<3;v++,i++)
    {
        funcs.Add( new Func<string>(delegate(){return "Hello "+ i++ +" "+v;}) );
    }
    foreach(var f in funcs)
        Console.WriteLine(f());
}

produces

Hello 3 3
Hello 4 3
Hello 5 3

After reading the explanation by Jon Skeet and Eric Lippert I thought I will get

Hello 3 3
Hello 3 3
Hello 3 3

Here both v and i are loop variables, while the value of i is picked up at that instant v is not why is this?. I don't understand the behavior.

like image 723
ferosekhanj Avatar asked Mar 25 '11 09:03

ferosekhanj


3 Answers

Well, you understood Eric and Jon correctly, but you missed one part of your code:

"Hello "+ i++ +" "+v;
          ^^^
          this part increments i for each call

So basically, what happens is similar to this:

  1. Capture the anonymous function 3 times, capturing references to variables in the method, not in the scope of the loop
  2. At the end of the loop, those two variables are both at value 3
  3. Execute the first function, outputting the contents of i and v, and then increment i
  4. Execute the second function, outputting the contents of i and v and since this is the same i as the previous method call, you will output 4 here, not 3
  5. and so on

If, on the other hand, you had changed your code by capturing variables inside the loop scope, like this:

for(int v=0,i=0;v<3;v++,i++)
{
    int ii = i, vv = v;
    funcs.Add( new Func<string>(delegate(){return "Hello "+ ii++ +" "+vv;}) );
}

Then you would get 0, 0, 1, 1, and 2, 2. You're still increasing the ii variable, you do it after using the captured value in the loop, but then you never use that variable again (each anonymous method gets its own private copy.) thanks @ferosekhanj for the comment

like image 88
Lasse V. Karlsen Avatar answered Oct 18 '22 01:10

Lasse V. Karlsen


The answer is simple: ++i is executed inside your delegate, thus incrementing the value each time. The first value will be 3 because that's the value of i after the loop.
Understand that your delegate is not executed inside your for loop but in the foreach loop.

like image 44
Daniel Hilgarth Avatar answered Oct 18 '22 00:10

Daniel Hilgarth


the result is correct (how could it not be? ;) ) When you execute the delegate, after the end of the loop, it will use the current value of the i and v variables.

v won't change anymore, v == 3 at the end of the loop. i == 3 too. But your delegate write i to the output, then increment it (i++). Therefore, each time the delegate is executed, i will be incremented, but not v.

This is what you are observing.

like image 2
Eilistraee Avatar answered Oct 18 '22 00:10

Eilistraee