循环中的 Lambda 变量捕获 - 这里会发生啥? [复制]
Posted
技术标签:
【中文标题】循环中的 Lambda 变量捕获 - 这里会发生啥? [复制]【英文标题】:Lambda variable capture in loop - what happens here? [duplicate]循环中的 Lambda 变量捕获 - 这里会发生什么? [复制] 【发布时间】:2012-10-12 14:46:27 【问题描述】:我正在努力解决问题,这里发生了什么?编译器会生成什么样的代码?
public static void vc()
var listActions = new List<Action>();
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
根据this,每次迭代都会创建一个新的 ActionHelper 实例。所以在那种情况下,我认为它应该打印 1..10。 有人可以给我一些编译器在这里做什么的伪代码吗?
谢谢。
【问题讨论】:
blogs.msdn.com/b/ericlippert/archive/2009/11/12/… 看来VS2013和.NET 4.5已经不是问题了 【参考方案1】:在这一行
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 关于主题的帖子,但这里有另一个:
Closure in a Loop Revisited
【讨论】:
我在我的回答中链接了它,但由于这个可能最终会在投票列表的顶部:blogs.msdn.com/b/ericlippert/archive/2009/11/12/… C# 5.0 于 2012 年 8 月发布,它包含在 Visual Studio 2012 中,因此从技术上讲它已经“修复”了。 @PaoloMoretti:还是更好地等待服务包 1? :)【参考方案2】:编译器生成什么样的代码?
听起来像你期望的那样:
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));
这清楚地表明您在每次迭代中都捕获了 same 变量。当您稍后实际执行该代码时,它们都引用了已递增到 10 的 same 值。
这不是编译器或运行时的错误...这是语言设计团队的一个有意识的决定。但是,由于对它的工作原理感到困惑,他们推翻了这一决定并decided to risk a breaking change to make it work more like you expect for C# 5。
【讨论】:
我认为对于新程序员来说,另一个常见的误解不是 foreach 生成不同的变量,而是 lambdas 关闭值而不是变量。 @Servy - 对于引用类型,在大多数情况下实际上是一样的。 嗯,不,真的,不是。如果您从未更改变量的值以引用另一个对象(就像您从未将值类型更改为另一个值一样),那么关闭变量或值都没关系。除非变量是只读的(或者至少在创建闭包和调用闭包之间没有改变),否则会有区别。此外,这是处理值类型,而不是引用类型。【参考方案3】:本质上,发生的事情是编译器在循环之外声明您的int i
,因此不会为每个操作创建一个新值,只是每次都引用同一个。到你执行代码时,i 是 10,所以它会打印很多 10。
【讨论】:
以上是关于循环中的 Lambda 变量捕获 - 这里会发生啥? [复制]的主要内容,如果未能解决你的问题,请参考以下文章