12

下面的代码输出 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

但是,此代码生成 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
4

4 回答 4

8

这称为“访问修改后的闭包”。基本上,只有一个i变量,所有三个 lambdas 都引用它。最后,一个i变量已递增为3,因此所有三个操作都打印3。(请注意,int loopScopedi = i只有在您稍后调用 lambda 时才会在 lambda 中运行。)

在第二个版本中,您int loopScopedi为每次迭代创建一个新的,并将其设置为 的当前值i,即012对于每次迭代。

您可以尝试想象内联 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
}
于 2013-05-28T21:43:47.823 回答
2

在 lambda 中捕获的变量被提升到 lambda 和外部代码之间共享的类中。

在您的第一个示例中,i被提升一次并for()与所有传递的 lambda 一起使用。当您到达Console.WriteLine时,i已经3for()循环到达。

在您的第二个示例中,loopScopedi每次循环运行都会提升一个新的,因此它不受后续循环的影响。

于 2013-05-28T21:45:25.713 回答
2

这是关于 C# 如何处理闭包的。在第一个示例中,不会正确捕获闭包,您最终将始终使用最后一个值;但在第二个示例中,您在占位符中捕获循环变量的当前值,然后使用该占位符;这提供了正确的解决方案。

C# 在 foreach 循环中捕获循环变量的方式与 C# 5.0 和以前版本中的 for 循环之间存在差异——这是一个重大变化。

我(几乎)有同样的问题,我在这里了解到了。

于 2013-05-28T21:47:03.833 回答
2

两者有什么区别?

范围不同。

在您的第一个循环中,您指的i是在循环语句范围中定义的变量,for而在第二个循环中您使用的是局部变量。333 输出是由于您的第一个循环迭代 3 次,因此i变量最终增加到 3,然后当您调用操作时,它们都引用同一个变量 ( i)。

在第二个循环中,您为每个 Action循环使用一个新变量,因此您得到 012。

于 2013-05-28T21:39:26.200 回答