これを行う (ローカルに割り当てる) 必要があるのは、foreach ループを使用しているか、Linq を使用している場合のみです。そうしないと、変更されたクロージャーで問題が発生する可能性があります。
これは MSDN ブログの抜粋です (以下のすべてのコンテンツはリンクからのものです)。
http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-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 で閉じられるため、常に正しい値が保持されます。
基本的に、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);
}
その後、コードは期待どおりに動作します。