5

I have a quick question hopefully about Action types and Lambdas in C#. Here's come code:

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

        for (int I = 0; I < 10; I++)
            actions.Add(new Action(() => Print(I.ToString())));

        foreach (Action a in actions)
        {
            a.Invoke();
        }
        actions.Clear();

        int X;
        for (X = 0; X < 10; X++)
        {
            int V = X;
            actions.Add(new Action(() => Print(V.ToString())));
        }

        foreach (Action a in actions)
        {
            a.Invoke();
        }
        Console.ReadLine();
    }


    public static void Print(string s)
    {
        Console.WriteLine(s);
    }

If you run this code you will see that it outputs 10, ten times in a row, then outputs the numbers 0-9 the second time around. It clearly has something to do with the way I use X vs I, and how I give my action a new variable V each time in the second loop... Possibly that each new V is a new address in memory, but I'm struggling to understand why I.ToString() doesn't do the same thing in the first loop... Why doesn't I.ToString() used in the first Action work in the same way as the second example?

Mark W
  • 2,791
  • 1
  • 21
  • 44
  • 1
    This might help: http://stackoverflow.com/questions/3168375/using-the-iterator-variable-of-foreach-loop-in-a-lambda-expression-why-fails – rory.ap Feb 16 '16 at 17:30

3 Answers3

5

The for loop is effectively expanded out to this by the compiler:

{
  int I;
  for (I = 0; I < 10; I++)
  {
    actions.Add(new Action(() => Print(I.ToString())));
  }
}

This means that all the lambda instances capture the same instance of I, which will be 10 when the loop exits.

In your second example you copy the value into a variable that is scoped to the body of the for statement, and the lambda captures this local. There will be a unique local for each repetition of the loop.

It's important to realize that you don't capture the value of the variable, rather you capture the variable itself. That's why the first example doesn't work, but the second one does.

Sean
  • 60,939
  • 11
  • 97
  • 136
  • Thanks for the explanation. I would have thought that I.ToString() was making a new variable, but clearly thats not the case. More like the function call I.ToString() is stored and that the value of I is subject to change, and not evaluated until invoke. – Mark W Feb 16 '16 at 17:46
2

It is because it is just a delegate and it gets executed when you you actually invoke it, and at time it is invoked all the actions have the last value which was set for i, while in foreach loop case it makes local copy of value so every action has it's own value which makes it print 0- 9.

In first example the i value gets evaluated when you invoke the delegate first time in foreach loop and at that time i has 10 in it, and in 2nd example you are storing the value in a local which mimics the same behaviour which is done by foreach loop, as foreach loop also makes a local copy of the value.

you can also read this explanation of Jon Skeet and there he is linking to 2 eric lippert's posts which will help you more.

Community
  • 1
  • 1
Ehsan Sajjad
  • 61,834
  • 16
  • 105
  • 160
1

When the Actions are created in your first loop, what is really saved is the call to the Print() method and the value of the variable I is not obtained until the calls to Invoke() method are made, but this occurs after the loop has finished and the variable I has a value of 10.

In the second case, you are creating a new variable V on each iteration, so that when you execute the actions each one is called with the value of the variable V that was created in that iteration.

Arturo Menchaca
  • 15,783
  • 1
  • 29
  • 53