关于闭包,使用for和foreach有什么区别?

4
演示代码存在一些问题。
var values = new List<int>() { 100, 110, 120 };  
var funcs = new List<Func<int>>();  

foreach(var v in values)   
    funcs.Add( ()=>v );  

foreach(var f in funcs)   
    Console.WriteLine(f());  

大多数人期望它是100/110/120,但实际上是120/120/120。

但是在vs2015和.net 4.5.1中,结果将输出100/110/120,而不是120/120/120。

当我按照以下方式测试代码时,forforeach之间存在一些差异。

var values = new List<int> {100, 110, 120};

var funcs = new List<Func<int>>();
foreach (var v in values)
    funcs.Add(() =>
    {
        Console.WriteLine(v);
        return v;
    } );

foreach (var f in funcs)
    Console.WriteLine(f());

//will throw exception
for (int i=0;i<values.Count;i++)
    funcs.Add(() =>
    {
        Console.WriteLine(values[i]);
        return values[i];
    });

foreach (var f in funcs)
    Console.WriteLine(f());

谁能给我更详细地解释一下闭包中的forforeach之间的区别?


你可能想阅读第二部分,其中包括更新 http://ericlippert.com/2009/11/16/closing-over-the-loop-variable-considered-harmful-part-two/ 。"我们正在进行重大变更。在C#5中,foreach的循环变量将在逻辑上位于循环内部,因此闭包将在每次关闭时关闭变量的新副本。for循环不会改变。现在我们回到原始文章。" - Chris
你看到页面顶部的更新说明了吗? - Nikita Shrivastava
1个回答

6
这是C#团队后悔的不幸决定的结果。由C# 5引入的一个破坏性变化最终改变了这种行为。引用Eric Lippert的话:
在C# 5中,foreach循环变量将逻辑上位于循环体内,因此闭包每次都会获得一个新的副本。
在C# 5之前,闭包都引用同一个变量绑定。因此,所有调用的输出结果都相同,因为它们都访问同一个变量的最新值。
然而,从C# 5开始,每次迭代都创建一个新的变量绑定。这是程序员最可能期望的行为。

2
值得注意的是,在for循环中,每次迭代必须明确使用相同的变量。 - Chris
@Chris 嗯,不是说一定要这样做,只是从语法上看似乎应该这样做。如果他们愿意的话,他们可以在每次迭代时创建变量的副本。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接