下面程序的输出结果是333而不是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(这样做是符合情理的,只要你把i看成是在每次循环迭代之间都保存其数值的变量,如果你愿意,你甚至可以在循环之间显式地改变i的数值)。因此委托被调用的时候,每个看到的i的值都是调用那一时刻i的数值,也就是3.我们可以把前面的循环展开以便更好地说明情况:
Action[] actions = new Action[3];
int i = 0;
actions[0] = () => Console.Write (i);
i = 1;
actions[1] = () => Console.Write (i);
i = 2;
actions[2] = () => Console.Write (i);
i = 3;
foreach (Action a in actions) a(); // 333
如果我们希望程序输出012,可以采用下面的解决方案:将迭代变量赋给一个本地变量,这个本地变量的作用域被限制在循环内部:
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
{
int loopScopedi = i;
actions [i] = () => Console.Write (loopScopedi);
}
foreach (Action a in actions) a(); // 012
这个方案使得闭包在每次循环都捕捉一个不同的数值。
在C# 5.0之前,foreach循环也遵循同样的原则
Action[] actions = new Action[3];
int i = 0;
foreach (char c in "abc")
actions [i++] = () => Console.Write (c);
foreach (Action a in actions) a(); // ccc in C# 4.0
这造成了很大的困扰:与for循环不同,foreach循环中的迭代变量是不可变的(immutable),因此通常都会将其作为一个循环内部的本地变量看待。好消息是这个问题在C# 5.0里已经被解决了,上面的例子在C# 5.0中得到的输出结果是"abc".
从学术上讲,这可以被称为一个破坏性变化(breaking change),因为在C# 5.0中重新编译一段C# 4.0代码会得到不同的结果。通常情况下,C#团队会尽量避免破坏性变化;但是,对于当前这个例子,这个破坏(break)几乎可以肯定意味着原来的C# 4.0程序有一个没有检测到的缺陷(bug),而不是基于对老版本的行为的透彻了解而刻意为之。