闭包在 for 和 foreach 循环中的行为不同

本文关键字:循环 for foreach 闭包 | 更新日期: 2023-09-27 18:34:18

在C#中试验闭包时,我发现如果它们在循环中捕获迭代器变量,它们会出乎意料地工作。

var actions = new List<Action>();
foreach (int i in new[] { 1, 2 })
    actions.Add(() => Console.WriteLine(i));
for (int i = 3; i <= 4; i++)
    actions.Add(() => Console.WriteLine(i));
foreach (var action in actions)
    action();

上面的代码产生了一个奇怪的结果(我使用的是.NET 4.5编译器):

1
2
5
5

为什么 2 个几乎相同的循环捕获i值不同?

闭包在 for 和 foreach 循环中的行为不同

在 C# 5 及更高版本中,foreach 循环为循环的每次迭代声明一个单独的 i 变量。因此,每个闭包都会捕获一个单独的变量,并且您会看到预期的结果。

for 循环中,您只有一个 i 变量,该变量由所有闭包捕获,并随着循环的进行而修改 - 因此当您调用委托时,您会看到该单个变量的最终值。

在 C# 2、3 和 4 中,foreach 循环的行为方式也是如此,这基本上从来都不是所需的行为,因此在 C# 5 中得到了修复。

如果在循环体的范围内引入新变量,则可以在for循环中实现相同的效果:

for (int i = 3; i <= 4; i++)
{
    int copy = i;
    actions.Add(() => Console.WriteLine(copy));
}

有关更多详细信息,请阅读 Eric Lippert 的博客文章,"关闭循环变量被认为是有害的" - 第 1 部分,第 2 部分。

在 foreach 情况下,它将值保存在局部变量中,因此它对每个委托都有自己的值,而在 for 循环情况下则不是这样,在 for 循环情况下,所有委托都引用相同的 i,因此所有委托都使用i中更新的最后一个值。

foreach循环的情况下,这是一个重大变化,在旧版本中,它们曾经的工作方式相同。