Action delegate uses the last values of variables declared outside foreach loop

I have this piece of code:

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

While the above code works as it should, this piece below:

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

…will always execute the delegates with the last values of i and tile variables. Why does this happen, and why do I have to make a local copy of those vars, especially non-reference type int i ?


Known "issue", please check Eric's blog Closures, captured variables.

Microsof decided to go for a breaking change, and fix it in C# 5.


You cannot use loop variables like this because by the time the delegate is executed the loop variable will likely be in its final (end of loop) state as it uses the value of the variable at the time the delete is executed, not created.

You need to make a local copy of the variable to get this to work:

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

By creating a local copy on each loop the value does not change and so your delegate will use the value that you expect.


This is expected: when you make a lambda, compiler creates a closure. It will capture the value of a temporary variable in there, but it would not capture the value of loop variables and other variables that change after creation of the lambda.

The core of the issue is that the delegate creation and execution times are different. The delegate object is created while the loop is running, but it is called well after the loop has completed. At the time the delegate is called, the loop variable has the value that it reached at the time the loop has completed, resulting in the effect that you see (the value does not change, and you see the last value from the loop).

Forgetting to create a temporary variable for use in closures is a mistake so common that popular code analyzers (eg ReSharper) warn you about it.

链接地址: http://www.djcxy.com/p/52782.html

上一篇: 在foreach中启动任务循环使用最后一项的值

下一篇: 动作委托使用在foreach循环之外声明的最后一个变量值