13

When you capture the iteration variable of a for loop, C# treats that variable as though it was declared outside the loop. This means that the same variable is captured in each iteration. The following program writes 333 instead of writing 012:

Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
    actions [i] = () => Console.Write (i);

foreach (Action a in actions) a(); // 333

I'm reading C# in a Nutshell (5th Edition) and today i came across this but i can't get my head over it, i don't get why the output is 333 and not 012. Is it because the value of i that's getting printed is the value after the loop? How is that possible? i is supposed to be disposed after the loop, isn't it?

JabberwockyDecompiler
  • 3,318
  • 2
  • 42
  • 54
Asad Ali
  • 684
  • 6
  • 17
  • @EhsanSajjad a is the lambda variable, in this case an `Action`. – DavidG May 25 '14 at 16:26
  • It's the variable of type `Action` declared in the foreach loop. – Asad Ali May 25 '14 at 16:26
  • 5
    Have a look at [Jon Skeet's article about closures](http://csharpindepth.com/articles/chapter5/closures.aspx) - it containts good examples and explanations. Another good articles are those of [Eric Lipper - closures](http://blogs.msdn.com/b/ericlippert/archive/tags/closures/). – keenthinker May 25 '14 at 16:32

3 Answers3

11

The variable i is captured inside the for loop but your are kind of extending the scope of it by doing so. So the variable is left at it's last state which was 3, hence the code outputting 333.

Another way to write the code is this:

Action[] actions = new Action[3];
int i; //declare i here instead of in the for loop

for (i = 0; i < 3; i++)
    actions [i] = () => Console.Write (i);

//Now i=3
foreach (Action a in actions) a(); // 333

The output is the same as writing:

Console.Write(i);
Console.Write(i);
Console.Write(i);
LW001
  • 2,452
  • 6
  • 27
  • 36
DavidG
  • 113,891
  • 12
  • 217
  • 223
  • So when is `i` disposed? I assume, since it's being treated like an outer-scope variable, that it gets garbage collected like other normal variables? – Asad Ali May 25 '14 at 16:30
  • 1
    @AsadAli It will be disposed when the last reference is gone, so when `actions` is disposed. – DavidG May 25 '14 at 16:33
  • but hoow @AsadAli i never saw this kind of scenarion in my 2 years work – Ehsan Sajjad May 25 '14 at 16:39
  • 8
    Be aware that since C# 5, `foreach` has been fixed to *not* place `i` outside the loop, but inside. In other words, if you changed the loop to this: `foreach (int i in Enumerable.Range(0,3))`, you'll actually get `012`. That fix does not apply to `for`, however. – JimmiTh May 25 '14 at 16:40
6

Because the lambda captures last value of i, and that is 3.Step of your loop is executed for last time,then i becomes 3 and your loop ends.

I think this would make it clear for you:

int i = 0;
for (; i < 3; i++) { }

Console.WriteLine(i);  // writes 3

You could fix this using a temporary variable:

for (int i = 0; i < 3; i++)
{
    int temp = i;
    actions[i] = () => Console.Write(temp);
}

foreach (Action a in actions) a(); // now: 012

I would recommend you to read this article to understand closures better

Selman Genç
  • 100,147
  • 13
  • 119
  • 184
2

My approach for understanding the closure in this case is to unroll the for loop:

var actions = new List<Action>();
// this loop is "executed"
for (int i = 0; i < 3; i++)
{
    actions.Add(() => Console.Write (i));
}
// pseudo "unroll" the loop
// i = 0
// action(0) = Console.WriteLine(i);
// i = 1
// action(1) = Console.WriteLine(i);
// i = 2
// action(2) = Console.WriteLine(i);
// i = 3

foreach (Action a in actions)
{
    a();
}
// pseudo "unroll" the foreach loop
// a(0) = Console.WriteLine(3); <= last value of i
// a(1) = Console.WriteLine(3); <= last value of i
// a(2) = Console.WriteLine(3); <= last value of i
// thus output is 333

// fix
var actions = new List<Action>();
// this loop is "executed"
for (int i = 0; i < 3; i++)
{
    var temp = i;
    actions.Add(() => Console.Write (temp));
}
// pseudo "unroll"
// i = 0
// temp = 0
// actions(0) => Console.WriteLine(temp); <= temp = 0
// i = 1
// temp = 1
// actions(1) => Console.WriteLine(temp); <= temp = 1
// i = 2
// temp = 2
// actions(2) => Console.WriteLine(temp); <= temp = 2
foreach (Action a in actions)
{
    a();
}
// pseudo "unroll" the foreach loop
// a(0) = Console.WriteLine(0); <= last value of first temp
// a(1) = Console.WriteLine(1); <= last value of second temp
// a(2) = Console.WriteLine(2); <= last value of third temp
// thus 012
keenthinker
  • 7,645
  • 2
  • 35
  • 45