你如何捕获迭代变量?

时间:2014-05-25 16:22:22

标签: c# .net for-loop lambda

当您捕获for循环的迭代变量时,C#将该变量视为在循环外声明。这意味着在每次迭代中捕获相同的变量。以下程序写入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

我在Nutshell(第5版)中阅读C#,今天我遇到了这个,但我无法理解它,我不明白为什么输出是{{1而不是333。是因为<{1}}打印的值是循环后的值吗?怎么可能?应该在循环之后处理012,不是吗?

3 个答案:

答案 0 :(得分:10)

变量i是在for循环中捕获的,但您可以通过这样做来扩展它的范围。因此变量保留在它的最后状态3,因此代码输出333。

编写代码的另一种方法是:

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

输出与写入相同:

Console.Write(i);
Console.Write(i);
Console.Write(i);

答案 1 :(得分:6)

因为lambda捕获了i的最后一个值,那就是3。循环步骤最后一次执行,然后我变成3并且你的循环结束了。

我认为这会让你明白:

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

Console.WriteLine(i);  // writes 3

您可以使用临时变量修复此问题:

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

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

我建议您阅读this article以更好地了解闭包

答案 2 :(得分:1)

我在这种情况下理解closure的方法是展开 for循环:

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