Lambda捕获问题与迭代器?
如果已经问过这个问题,请道歉,但假设我们有这个代码(我用Mono 2.10.2运行它并用gmcs
2.10.2.0编译):
using System; public class App { public static void Main(string[] args) { Func f = null; var strs = new string[]{ "foo", "bar", "zar" }; foreach (var str in strs) { if ("foo".Equals(str)) f = () => str; } Console.WriteLine(f()); // [1]: Prints 'zar' foreach (var str in strs) { var localStr = str; if ("foo".Equals(str)) f = () => localStr; } Console.WriteLine(f()); // [2]: Prints 'foo' { int i = 0; for (string str; i str; }} Console.WriteLine(f()); // [3]: Prints 'zar' } }
[1]
打印与[3]
相同似乎是合乎逻辑的。 但说实话,我不知何故预计它会打印出与[2]
相同的内容。 我不知何故相信[1]
的实现会更接近[2]
。
问题 :任何人都可以提供对规范的引用,其中它确切地告诉str
变量(或者甚至是迭代器)如何被[1]
的lambda捕获。
我想我正在寻找的是foreach
循环的确切实现。
您要求参考规范; 相关位置是第8.8.4节,其中声明“foreach”循环等效于:
V v; while (e.MoveNext()) { v = (V)(T)e.Current; embedded-statement }
请注意,值v在while循环之外声明,因此存在单个循环变量。 然后由lambda关闭。
UPDATE
因为有很多人遇到这个问题,C#设计和编译团队改变了C#5来拥有这些语义 :
while (e.MoveNext()) { V v = (V)(T)e.Current; embedded-statement }
然后具有预期的行为 – 每次都关闭不同的变量。 从技术上讲,这是一个突破性的变化,但依赖于您正在经历的奇怪行为的人数希望非常小。
请注意,在这方面,C#2,3和4现在与C#5不兼容。 另请注意,更改仅适用于foreach
,而不适用于for
循环。
有关详细信息,请参见http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/ 。
Commenter abergmeier说:
C#是唯一具有这种奇怪行为的语言。
这句话绝对是错误的 。 考虑以下JavaScript:
var funcs = []; var results = []; for(prop in { a : 10, b : 20 }) { funcs.push(function() { return prop; }); results.push(funcs[0]()); }
像是,你是否愿意猜测results
的内容是什么?
1/3和2之间的核心差异是正在捕获的变量的生命周期。 在1和3中,lambda捕获迭代变量str
。 在for
循环和foreach
循环中,循环的生命周期都有一个迭代变量。 当lambda在循环结束时执行时,它将使用最终值: zar
在2中,您捕获的是一个局部变量,其生命周期是循环的单个迭代。 因此,您捕获当时的值“foo”
我能告诉你的最好的参考是Eric关于这个主题的博客文章
循环1和3中发生以下情况:
当前值分配给变量str
。 它始终是相同的变量,每次迭代中只有不同的值。 该变量由lambda捕获。 由于lambda在循环结束后执行,因此它具有数组中最后一个元素的值。
循环2中发生以下情况:
将当前值分配给新变量localStr
。 它始终是一个获取分配值的新变量。 这个新变量由lambda捕获。 因为循环的下一次迭代会创建一个新变量,所以捕获的变量的值不会改变,因此它会输出“foo”。
对于来自谷歌的人
我用这种方法修复了lambda bug:
我改变了这个
for(int i=0;i<9;i++) btn.OnTap += () => { ChangeCurField(i * 2); };
对此
for(int i=0;i<9;i++) { int numb = i * 2; btn.OnTap += () => { ChangeCurField(numb); }; }
这迫使“麻木”变量成为lambda的唯一变量,并且此时也生成生成而不是当lambda被调用/生成时<不确定它何时发生。