如何在 lambda 表达式中捕获外部变量的值? [英] How can I capture the value of an outer variable inside a lambda expression?

查看:41
本文介绍了如何在 lambda 表达式中捕获外部变量的值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚刚遇到以下行为:

for (var i = 0; i < 50; ++i) {
    Task.Factory.StartNew(() => {
        Debug.Print("Error: " + i.ToString());
    });
}

会导致一系列错误:x",其中大部分 x 等于 50.

Will result in a series of "Error: x", where most of the x are equal to 50.

同样:

var a = "Before";
var task = new Task(() => Debug.Print("Using value: " + a));
a = "After";
task.Start();

将导致使用值:之后".

Will result in "Using value: After".

这显然意味着 lambda 表达式中的连接不会立即发生.在声明表达式时,如何在 lambda 表达式中使用外部变量的副本?以下内容不会更好(我承认这不一定是不连贯的):

This clearly means that the concatenation in the lambda expression does not occur immediately. How is it possible to use a copy of the outer variable in the lambda expression, at the time the expression is declared? The following will not work better (which is not necessarily incoherent, I admit):

var a = "Before";
var task = new Task(() => {
    var a2 = a;
    Debug.Print("Using value: " + a2);
});
a = "After";
task.Start();

推荐答案

这更多地与 lambda 相关,而不是线程.lambda 捕获对变量的引用,而不是变量的值.这意味着当您尝试在代码中使用 i 时,它的值将是最后存储在 i 中的任何值.

This has more to do with lambdas than threading. A lambda captures the reference to a variable, not the variable's value. This means that when you try to use i in your code, its value will be whatever was stored in i last.

为避免这种情况,您应该在 lambda 启动时将变量的值复制到局部变量.问题是,启动任务有开销,第一个副本可能只有在循环完成后才能执行.下面的代码也会失败

To avoid this, you should copy the variable's value to a local variable when the lambda starts. The problem is, starting a task has overhead and the first copy may be executed only after the loop finishes. The following code will also fail

for (var i = 0; i < 50; ++i) {
    Task.Factory.StartNew(() => {
        var i1=i;
        Debug.Print("Error: " + i1.ToString());
    });
}

正如 James Manning 所指出的,您可以在循环中添加一个局部变量并将循环变量复制到那里.这样你就创建了 50 个不同的变量来保存循环变量的值,但至少你得到了预期的结果.问题是,你确实得到了很多额外的分配.

As James Manning noted, you can add a variable local to the loop and copy the loop variable there. This way you are creating 50 different variables to hold the value of the loop variable, but at least you get the expected result. The problem is, you do get a lot of additional allocations.

for (var i = 0; i < 50; ++i) {
    var i1=i;
    Task.Factory.StartNew(() => {
        Debug.Print("Error: " + i1.ToString());
    });
}

最好的解决方案是将循环参数作为状态参数传递:

The best solution is to pass the loop parameter as a state parameter:

for (var i = 0; i < 50; ++i) {
    Task.Factory.StartNew(o => {
        var i1=(int)o;
        Debug.Print("Error: " + i1.ToString());
    }, i);
}

使用状态参数会减少分配.查看反编译的代码:

Using a state parameter results in fewer allocations. Looking at the decompiled code:

  • 第二个代码段将创建 50 个闭包和 50 个委托
  • 第三个代码段将创建 50 个装箱整数,但只有一个委托

这篇关于如何在 lambda 表达式中捕获外部变量的值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆