唯一需要执行此操作(本地分配)的情况是您在 foreach 循环中或使用 Linq。否则,您可能会遇到修改后的闭包问题。
这是来自 MSDN 博客的片段(以下所有内容均来自链接)。
http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closure-over-the-loop-variable-considered-harmful.aspx
但我正在超越自己。这个片段的输出是什么?
var values = new List<int>() { 100, 110, 120 };
var funcs = new List<Func<int>>();
foreach(var v in values)
funcs.Add( ()=>v );
foreach(var f in funcs)
Console.WriteLine(f());
大多数人期望它是 100 / 110 / 120。实际上是 120 / 120 / 120。为什么?
因为 ()=>v 的意思是“返回变量 v 的当前值”,而不是“返回创建委托时返回的值 v”。闭包关闭变量,而不是值。并且当方法运行时,很明显分配给 v 的最后一个值是 120,所以它仍然具有该值。
这非常令人困惑。编写代码的正确方法是:
foreach(var v in values)
{
var v2 = v;
funcs.Add( ()=>v2 );
}
现在会发生什么?每次我们重新启动循环体时,我们都会在逻辑上创建一个全新的变量 v2。每个闭包都在不同的 v2 上关闭,该 v2 只分配一次,因此它始终保持正确的值。
基本上,问题的出现是因为我们指定 foreach 循环是一个语法糖
{
IEnumerator<int> e = ((IEnumerable<int>)values).GetEnumerator();
try
{
int m; // OUTSIDE THE ACTUAL LOOP
while(e.MoveNext())
{
m = (int)(int)e.Current;
funcs.Add(()=>m);
}
}
finally
{
if (e != null) ((IDisposable)e).Dispose();
}
}
如果我们指定扩展是
try
{
while(e.MoveNext())
{
int m; // INSIDE
m = (int)(int)e.Current;
funcs.Add(()=>m);
}
那么代码将按预期运行。