3

我有这段代码:

int i = 0;
foreach(var tile in lib.dic.Values)
{
    var ii = i;
    var t = tile;
    Button b = new Button( () = > { MainStatic.tile = t; } );
    Checkbox c = new Checkbox( () = > { lib.arr[ii].b = !lib.arr[ii].b; } );
    i++;
}

虽然上面的代码可以正常工作,但下面的这段代码:

int i = 0;
foreach(var tile in lib.dic.Values)
{
    Button b = new Button( () = > { MainStatic.tile = tile; } );
    Checkbox c = new Checkbox( () = > { lib.arr[i].b = !lib.arr[i].b; } );
    i++;
}

i…将始终使用和tile变量的最后值执行委托。为什么会发生这种情况,为什么我必须制作这些变量的本地副本,尤其是非引用类型int i

4

3 回答 3

3

已知的“问题”,请查看 Eric 的博客Closures,捕获变量。

Microsoft 决定进行重大更改,并在 C# 5 中修复它。

于 2013-01-06T17:09:54.127 回答
3

这是意料之中的:当你创建一个 lambda 时,编译器会创建一个闭包。它将在那里捕获临时变量的值,但不会捕获循环变量和其他在创建 lambda 后发生变化的变量的值。

问题的核心是委托的创建和执行时间不同。委托对象是在循环运行时创建的,但在循环完成后调用它。在调用委托时,循环变量具有在循环完成时达到的值,从而产生您所看到的效果(值没有改变,并且您看到循环中的最后一个值)。

忘记创建用于闭包的临时变量是一个非常常见的错误,以至于流行的代码分析器(例如 ReSharper)会警告您。

于 2013-01-06T17:11:36.777 回答
2

您不能像这样使用循环变量,因为在执行委托时,循环变量可能处于其最终(循环结束)状态,因为它使用的是执行删除时变量的值,而不是创建时。

您需要制作变量的本地副本才能使其正常工作:

int i = 0;
foreach(var tile in lib.dic.Values)
{
    var tileForClosure = tile;
    var iForClosure = i;
    Button b = new Button( () = > { MainStatic.tile = tileForClosure ; } );
    Checkbox c = new Checkbox( () = > { lib.arr[iForClosure].b = !lib.arr[iForClosure].b; } );
    i++;
}

通过在每个循环上创建一个本地副本,值不会改变,因此您的委托将使用您期望的值。

于 2013-01-06T17:11:46.147 回答