动作委托使用foreach循环外声明的变量的最后值

本文关键字:声明 变量 最后 循环 foreach | 更新日期: 2023-09-27 17:50:47

我有这段代码:

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++;
}

…将始终执行具有itile变量最后值的委托。为什么会发生这种情况,为什么我必须在本地复制这些变量,特别是非引用类型int i ?

动作委托使用foreach循环外声明的变量的最后值

已知"问题",请查看Eric的博客闭包,捕获变量。

微软决定做一个突破性的改变,并在c# 5中修复它

这是预期的:当你创建一个lambda时,编译器会创建一个闭包。它将捕获其中的临时变量的值,但它不会捕获循环变量的值以及在创建lambda后更改的其他变量的值。

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

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

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

你需要做一个变量的本地拷贝来让它工作:

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++;
}

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