【问题标题】:Closures behaving differently in for and foreach loops闭包在 for 和 foreach 循环中表现不同
【发布时间】:2016-05-23 18:21:33
【问题描述】:

在 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 的值不同?

【问题讨论】:

  • 你很聪明地注意到了这种微妙的区别。是的,这里有一个危险,有人可以将 foreach 重构为 for 而没有意识到他们正在引入语义更改!我很想知道您是否有“for”案例的“真实世界”用例,特别是“for”的奇怪行为是所需行为的案例。

标签: c# for-loop closures


【解决方案1】:

在 foreach 情况下,它将值保存在局部变量中,因此它对每个委托都有自己的值,而在 for 循环情况下并非如此,在 for 循环情况下,所有委托都引用相同的 i 所以最后一个i 中更新的值被所有代表使用。

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

【讨论】:

    【解决方案2】:

    在 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 的博文,“关闭被认为有害的循环变量”-part 1part 2

    【讨论】:

      猜你喜欢
      • 2011-05-19
      • 2018-02-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-02
      相关资源
      最近更新 更多