我刚刚遇到了最意想不到的行为。我确信这有一个很好的理由。有人可以帮忙解释一下吗?
考虑以下代码:
var nums = new int[] { 1, 2, 3, 4 };
var actions = new List<Func<int>>();
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'而不是它返回的结果吗?
答案 0 :(得分:7)
这是关于lambdas的众所周知和已确立的行为,尽管对于那些第一次遇到它的人来说经常会感到惊讶。根本问题是你的lambda 的心理模型不太正确。
lambda是一个在调用之前不会运行的函数。你的闭包绑定了对该lambda实例的引用,而不是值。当您在最终的foreach循环中执行操作时,这是您第一次实际关注已关闭的引用以查看它是什么。
在第一种情况下,你正在引用num,并且在那时,num的值是4,所以当然你的所有输出都是4.在第二种情况下,每个lambda被绑定到一个不同的值,每次都是循环的本地,并且该值不会更改(它仅仅因为lambda引用而没有GC。)因此,您得到了您期望的答案。
关于本地临时值的闭包实际上是从lambda中的某个时间点捕获特定值的标准方法。
Adam的link to Eric Lippert's blog提供了更深入(技术上准确)的描述。
答案 1 :(得分:3)
关于此问题,请参阅Eric Lippert's blog post;它与代码中迭代器变量作用域的方式有关,以及它如何应用于lambda闭包和提升函数。
答案 2 :(得分:2)
由于foreach
构造只是语法糖,所以最好以它的真实形式来考虑它。
int num;
while (nums.MoveNext())
{
num = nums.Current;
actions.Add(() => num);
}
lambda将捕获num
变量,因此当您执行lambda时,将使用num
的最新值。
答案 3 :(得分:1)
这是因为以下两件事:
1)委托保存外部变量的上下文(范围)
2)第一个foreach循环将只编译一个声明的“num”变量
3)懒惰的评价
在第一个循环中添加的每个委托将保存保存到作用域的相同num变量。由于延迟评估,您将在第一个周期结束后运行委托,因此保存到委托的范围等于4,等等。
答案 4 :(得分:1)