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

发布于 2024-08-24 17:02:34 字数 542 浏览 6 评论 0原文

我刚刚遇到了最意想不到的行为。我确信它以这种方式工作是有充分理由的。有人可以帮忙解释一下吗?

考虑一下这段代码:

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”,而不是它返回的结果吗?

I just came across the most unexpected behavior. I'm sure there is a good reason it works this way. Can someone help explain this?

Consider this code:

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() + " ");
}

The output is a bit surprising for me:

4 4 4 4 1 2 3 4 

Obviously there's something going on with how the lambda is referencing the enumerator. In the first version of the foreach, is 'num' actually bound to 'Current', instead of the result returned by it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

め可乐爱微笑 2024-08-31 17:02:34

这是关于 lambda 的众所周知且既定的行为,尽管对于那些第一次遇到它的人来说常常会感到惊讶。根本问题是您对 lambda 是什么的心智模型不太正确。

lambda 是一个在调用之前不会运行的函数。您的闭包绑定了对该 lambda 实例的引用,而不是值。当您在最终的 foreach 循环中执行操作时,这是您第一次真正遵循封闭引用来查看它是什么。

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

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

Adam 的 链接Eric Lippert 的博客提供了对正在发生的事情的更深入(并且技术上准确)的描述。

This is well-known and established behavior regarding lambdas, though frequently surprising to those who've encountered it for the first time. The fundamental issue is that your mental model of what a lambda is isn't quite correct.

A lambda is a function that doesn't get run until it's invoked. Your closure binds a reference to that lambda instance, not the value. When you execute your actions in your final foreach loop, that's the first time you're actually following the closed reference to see what it is.

In the first case, you're referencing num, and at that point, the value of num is 4, so of course all your output is 4. In the second case, each lambda has been bound to a different value that was local to the loop each time, and that value isn't changed (it hasn't been GC'd solely because of the lambda reference.) therefore, you get the answer that you expect.

The closure over a local temporary value is actually the standard approach to capture a specific value from a point in time within the lambda.

Adam's link to Eric Lippert's blog provides a more in-depth (and technically accurate) description of what's going on.

南巷近海 2024-08-31 17:02:34

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

See Eric Lippert's blog post on this issue; it has to do with how iterator variables are scoped in code, and how that applies to lambda closures and hoisted functions.

花伊自在美 2024-08-31 17:02:34

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

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

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

Since the foreach construct is just syntactic sugar it is best to think of it in it's true form.

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

The lambda will capture the num variable so when you execute the lambda the latest value of num will be used.

此生挚爱伱 2024-08-31 17:02:34

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

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

This is because of two following things:
1) delegates save context (scope) of outside variables
2) first foreach cycle will compile in only one "num" variable declared.
3) lazy evaluation

Each delegated added in the first cycle will save the same num variable saved to the scope. Because of lazy evaluation you will run the delegates after the first cycle is finished, so num veraible, saved to the delegates' scope equals 4.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文