以下代码输出33而不是012.我不明白为什么在每次迭代中没有捕获新变量loopScopedi而不是捕获相同的变量。
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
{
actions [i] = () => {int loopScopedi = i; Console.Write (loopScopedi);};
}
foreach (Action a in actions) a(); // 333
Hopwever,这段代码产生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
答案 0 :(得分:7)
这称为“访问修改后的闭包”。基本上,只有一个i
变量,而且所有三个lambd都指的是它。最后,一个i
变量已增加到3
,因此所有三个操作都会打印3
。 (请注意,lambda中的int loopScopedi = i
仅在稍后调用lambda后才会运行。)
在第二个版本中,您为每次迭代创建一个新int loopScopedi
,并将其设置为当前值i
,即0
和1
, 2
,每次迭代。
您可以尝试想象内联lambda以更清楚地了解正在发生的事情:
foreach (Action a in actions)
{
int loopScopedi = i; // i == 3, since this is after the for loop
Console.Write(loopScopedi); // always outputs 3
}
对战:
foreach (Action a in actions)
{
// normally you could not refer to loopScopedi here, but the lambda lets you
// you have "captured" a reference to the loopScopedi variables in the lambda
// there are three loopScopedis that each saved a different value of i
// at the time that it was allocated
Console.Write(loopScopedi); // outputs 0, 1, 2
}
答案 1 :(得分:2)
两者之间有什么区别?
范围不同。
在第一个循环中,您指的是在i
循环语句范围中定义的for
变量,而在第二个循环中您使用的是局部变量。 333输出的结果是你的第一个循环迭代3次,因此i
变量最终增加到3,然后当你调用动作时,它们都引用相同变量(i
)。
在第二个循环中,您为每个 Action
使用了一个新变量,因此您获得了012。
答案 2 :(得分:2)
在lambda中捕获的变量被提升到lambda和外部代码之间共享的类中。
在您的第一个示例中,i
被提升一次并与for()
和所有传递的lambdas一起使用。当您到达Console.WriteLine
时,i
已从3
循环达到for()
。
在您的第二个示例中,每次循环运行时都会挂起一个新的loopScopedi
,因此它不会受到后续循环的影响。
答案 3 :(得分:2)
这是关于C#如何处理闭包。在第一个示例中,将无法正确捕获闭包,您将最终使用最后一个值;但在第二个示例中,您将在占位符中捕获循环变量的当前值,然后使用该占位符;这提供了正确的解决方案。
C#捕获foreach循环中的循环变量和C#5.0以及之前版本中的循环之间存在差异 - 这是一个重大变化。
我(几乎)有同样的问题,我了解了它here。