C#中的lambda如何绑定到foreach中的枚举器?

我刚刚遇到了最意想不到的行为。 我确信这有一个很好的理由。 有人可以帮忙解释一下吗?

考虑以下代码:

var nums = new int[] { 1, 2, 3, 4 }; var actions = new List<Func>(); foreach (var num in nums) { actions.Add(() => num); } foreach (var num in nums) { var x = num; actions.Add(() => x); } foreach (var action in actions) { Debug.Write(action() + " "); } 

输出对我来说有点意外:

 4 4 4 4 1 2 3 4 

显然有一些关于lambda如何引用枚举器的事情。 在foreach的第一个版本中,’num’实际上绑定到’Current’,而不是它返回的结果?

这是关于lambdas的众所周知和已确立的行为,尽管对于那些第一次遇到它的人来说经常是令人惊讶的。 根本问题在于你对lambda 什么的心智模型并不完全正确。

lambda是一个在调用之前不会运行的函数。 你的闭包绑定了对该lambda实例的引用,而不是值。 当您在最后的foreach循环中执行操作时,这是您第一次实际关注已关闭的引用以查看它是什么。

在第一种情况下,您正在引用num,并且此时,num的值为4,因此当然所有输出都是4.在第二种情况下,每个lambda已绑定到本地的不同值每次循环,并且该值不会更改(它仅仅因为lambda引用而没有GC。)因此,您得到了您期望的答案。

对本地临时值的闭包实际上是从lambda中的某个时间点捕获特定值的标准方法。

Adam 与Eric Lippert博客的链接提供了更加深入(并且技术上准确)的描述。

请参阅Eric Lippert关于此问题的博客文章 ; 它与迭代器变量如何在代码中作用域有关,以及它如何应用于lambda闭包和提升函数。

由于foreach构造只是语法糖,因此最好以它的真实forms来考虑它。

 int num; while (nums.MoveNext()) { num = nums.Current; actions.Add(() => num); } 

lambda将捕获num变量,因此当您执行lambda时,将使用num的最新值。

这是因为以下两件事:
1)委托保存外部变量的上下文(范围)
2)第一个foreach循环将只编译一个声明的“num”变量。
3)懒惰的评价

在第一个循环中添加的每个委托将保存保存到作用域的相同num变量。 由于延迟评估,您将在第一个周期完成后运行委托,因此保存到委托的范围等于4。

请参阅关于lambda,捕获和可变性