循环中的Lambda变量捕获 – 这里发生了什么?
我想试试看,这里发生了什么? 编译器生成什么样的代码?
public static void vc() { var listActions = new List(); foreach (int i in Enumerable.Range(1, 10)) { listActions.Add(() => Console.WriteLine(i)); } foreach (Action action in listActions) { action(); } } static void Main(string[] args) { vc(); }
输出: 10 10 .. 10
据此,将为每次迭代创建一个ActionHelper的新实例。 那么在这种情况下,我认为它应该打印1..10。 有人能给我一些编译器在这里做的伪代码吗?
谢谢。
在这一行
listActions.Add(() => Console.WriteLine(i));
捕获变量i
,或者如果您愿意,创建一个指向该变量的内存位置的指针 。 这意味着每个委托都有一个指向该内存位置的指针 。 执行此循环后:
foreach (int i in Enumerable.Range(1, 10)) { listActions.Add(() => Console.WriteLine(i)); }
由于显而易见的原因, i
是10
,因此Action
(s)中指向的所有指针所指向的内存内容变为10。
换句话说, i
被捕获了。
请注意,根据Eric Lippert的说法,这种“奇怪”行为将在C# 5.0
得到解决 。
因此在C# 5.0
您的程序将按预期打印:
1,2,3,4,5...10
编辑:
找不到Eric Lippert关于主题的post,但这是另一个:
重新审视循环中的闭包
编译器生成什么样的代码?
这听起来像你期望的那样:
foreach (int temp in Enumerable.Range(1, 10)) { int i = temp; listActions.Add(() => Console.WriteLine(i)); }
对每次迭代使用不同的变量,因此将捕获创建lambda时变量的值。 实际上,您可以使用这个确切的代码并获得您想要的结果。
但是编译器实际上做的更接近于此:
int i; foreach (i in Enumerable.Range(1, 10)) { listActions.Add(() => Console.WriteLine(i)); }
这清楚地表明您在每次迭代时捕获相同的变量。 当您稍后去实际执行该代码时,它们都会引用已经增加到10的相同值。
这不是编译器或运行时中的错误……这是语言设计团队的一个有意识的决定。 然而,由于对这是如何工作的困惑,他们已经改变了这个决定,并决定冒险进行突破性改变,使其更像你期望的C#5 。
本质上发生的事情是编译器在循环之外声明你的int i
,因此不为每个动作创建它的新值,每次只引用相同的一个。 当你执行代码时,我是10,所以它打印10很多。
上述就是C#学习教程:循环中的Lambda变量捕获 – 这里发生了什么?分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/1002307.html