我怎样才能捕捉到一个lambda前pression内的外变量的值? [英] How can I capture the value of an outer variable inside a lambda expression?

查看:121
本文介绍了我怎样才能捕捉到一个lambda前pression内的外变量的值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正好遇到以下行为:

 为(VAR I = 0; I< 50; ++ I){
    Task.Factory.StartNew(()=> {
        Debug.Print(错误:+ i.ToString());
    });
}

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

同样的:

  VAR一个=之前;
VAR任务=新建任务(()=> Debug.Print(使用值:+ a)条);
A =经过;
task.Start();

将导致使用值:在

这显然意味着,在拉姆达前pression串接不会立即发生。这怎么可能使用外部变量的副本拉姆达前pression,在除权pression定义的时候?以下将无法正常工作更好(这不一定是语无伦次,我承认):

  VAR一个=之前;
VAR任务=新建任务(()=> {
    VAR A2 = A;
    Debug.Print(使用值:+ A2);
});
A =经过;
task.Start();


解决方案

这更多的是与lambda表达式比线程。一个lambda捕获引用一个变量,而不是变量的值。这意味着,当您尝试使用的 I 的在code,将其值无论是储存在的 I 的最后一次。

要避免这种情况,你应该在lambda启动时变量的值复制到本地变量。问题是,在开始任务具有开销和循环结束后,才第一次副本可能被执行。 以下code也将失败

 为(VAR I = 0; I< 50; ++ I){
    Task.Factory.StartNew(()=> {
        VAR I1 = I;
        Debug.Print(错误:+ i1.ToString());
    });
}

由于詹姆斯·曼宁指出,你可以添加一个局部变量循环并复制循环变量存在。这样,您要创建50个不同的变量来保存循环变量的值,但至少你得到预期的结果。问题是,你得到了很多的额外拨款。

 为(VAR I = 0; I< 50; ++ I){
    VAR I1 = I;
    Task.Factory.StartNew(()=> {
        Debug.Print(错误:+ i1.ToString());
    });
}

最好的解决办法是通过循环参数作为状态参数:

 为(VAR I = 0; I< 50; ++ I){
    Task.Factory.StartNew(O => {
        VAR I1 =(INT)O;
        Debug.Print(错误:+ i1.ToString());
    }, 一世);
}

在更少的分配使用状态参数的结果。纵观反编译code:


  • 第二个片段将创建50封和50名代表

  • 第三个片段将创造50盒装的整数,但只有一个代表

I just encountered the following behavior:

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

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

Similarly:

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

Will result in "Using value: After".

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();

解决方案

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.

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

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:

  • the second snippet will create 50 closures and 50 delegates
  • the third snippet will create 50 boxed ints but only a single delegate

这篇关于我怎样才能捕捉到一个lambda前pression内的外变量的值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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