3

私はこのコードを持っています:

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ですか?

4

3 に答える 3

3

既知の「問題」については、Eric のブログClosures、captured variables を確認してください。

Microsof は破壊的な変更を行うことを決定し、C# 5 で修正しました。

于 2013-01-06T17:09:54.127 に答える
3

これは予想どおりです。ラムダを作成すると、コンパイラはクロージャを作成します。そこに一時変数の値をキャプチャしますが、ラムダの作成後に変更されるループ変数やその他の変数の値はキャプチャしません。

問題の核心は、デリゲートの作成時間と実行時間が異なることです。デリゲート オブジェクトはループの実行中に作成されますが、ループが完了した後で呼び出されます。デリゲートが呼び出された時点で、ループ変数には、ループが完了した時点で到達した値が含まれているため、実際の効果が得られます (値は変化せず、ループの最後の値が表示されます)。

クロージャーで使用する一時変数を作成し忘れることはよくある間違いであり、一般的なコード アナライザー (ReSharper など) はそれについて警告します。

于 2013-01-06T17:11:36.777 に答える
2

このようにループ変数を使用することはできません。これは、デリゲートが実行されるまでに、ループ変数が最終 (ループの終了) 状態になる可能性があるためです。これは、削除が実行された時点の変数の値を作成するのではなく使用するためです。

これを機能させるには、変数のローカル コピーを作成する必要があります。

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

各ループでローカル コピーを作成することにより、値は変更されないため、デリゲートは期待どおりの値を使用します。

于 2013-01-06T17:11:46.147 に答える