Action delegate uses the last values of variables declared outside foreach loop
我有这段代码:
1 2 3 4 5 6 7 8 9
| 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 ++;
} |
虽然上面的代码可以正常工作,但下面这段代码是:
1 2 3 4 5 6 7
| 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变量的最后一个值执行委托。为什么会发生这种情况,为什么我必须对这些var进行本地复制,尤其是非引用类型int i?
- 这是一个非常常见的问题。请参阅stackoverflow.com/questions/8898925/…
已知"问题",请检查埃里克的博客关闭,捕获变量。
微软决定进行一次突破性的变革,并在C 5中修复。
- 我不会把它描述成一个"已知的问题"——这是设计出来的。
- @柯林马克真的。我知道A我会有这样的评论,但是他们似乎已经在C 5中修正了它。
- 有趣的是,我不知道他们在C 5中改变了它。也许太多人不明白发生了什么。我想不出为什么我会想要这种老行为,但我相信现在肯定有人会依赖它。
- @我两天前重读了这篇博文:)
不能使用这样的循环变量,因为在执行委托时,循环变量可能处于其最终(循环结束)状态,因为它使用执行删除时变量的值,而不是创建时变量的值。
您需要制作变量的本地副本才能使其正常工作:
1 2 3 4 5 6 7 8 9
| 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 ++;
} |
通过在每个循环上创建本地副本,该值不会更改,因此您的委托将使用您期望的值。
这是预期的:当您生成lambda时,编译器会创建一个闭包。它将捕获其中临时变量的值,但不会捕获循环变量的值以及创建lambda后更改的其他变量的值。
问题的核心是委托的创建和执行时间不同。委托对象是在循环运行时创建的,但在循环完成后调用。在调用委托时,循环变量具有它在循环完成时达到的值,从而产生您看到的效果(该值不会更改,并且您看到循环中的最后一个值)。
忘记创建一个用于闭包的临时变量是一个非常常见的错误,以至于流行的代码分析程序(例如resharper)会警告您。