質問
次のコードを想定します:
foreach(Item i on ItemCollection)
{
Something s = new Something();
s.EventX += delegate { ProcessItem(i); };
SomethingCollection.Add(s);
}
もちろん、すべてのデリゲートが同じItemを指しているため、これは間違っています。代替手段は次のとおりです。
foreach(Item i on ItemCollection)
{
Item tmpItem = i;
Something s = new Something();
s.EventX += delegate { ProcessItem(tmpItem); };
SomethingCollection.Add(s);
}
この場合、すべてのデリゲートは自分のアイテムを指します。
このアプローチはどうですか?他のより良い解決策はありますか?
解決
更新:この問題に関する広範な分析と解説がここにあります:
http: //ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/
これは非常に頻繁に報告される問題です。通常、これはコンパイラのバグとして報告されますが、実際には、コンパイラは仕様に従って正しいことをしています。無名関数は values ではなく、変数を閉じます。また、foreachループ変数は1つしかありません。したがって、各ラムダは同じ変数を閉じ、その変数の現在の値を取得します。
これはほとんどすべての人にとって驚くべきことであり、多くの混乱と多くのバグ報告につながります。将来の仮想バージョンの仕様と実装を変更して、ループ変数がループ構造内で論理的に宣言されるように検討し、" fresh"ループを通して毎回変数。
これは重大な変更になりますが、この奇妙な動作に依存している人の数は非常に少ないと思います。この件について意見がある場合は、上記のアップデートで言及したブログ投稿にコメントを追加してください。ありがとう!
他のヒント
コードの2番目のチャンクは、他のすべてのものを同じ状態に保つための最良のアプローチです。
ただし、 Item
をとる Something
にプロパティを作成することは可能です。次に、イベントコードは、イベントの送信者からこの Item
にアクセスするか、イベントのeventargsに含まれる場合があります。したがって、閉鎖の必要がなくなります。
個人的に「不要な閉鎖の解消」を追加しました。それらを推論するのは難しい可能性があるため、価値のあるリファクタリングとして。
ItemCollectionが(generic)List の場合、使用できます ForEach -メソッド。 iごとに新しいスコープを提供します:
ItemCollection.ForEach(
i =>
{
Something s = new Something();
s.EventX += delegate { ProcessItem(i); };
SomethingCollection.Add(s);
});
または、他の適切な Linq -methodを使用できます- 選択:
var somethings = ItemCollection.Select(
i =>
{
Something s = new Something();
s.EventX += delegate { ProcessItem(i); };
return s;
});
foreach(Something s in somethings)
SomethingCollection.Add(s);
ここで直面している問題は、閉鎖などの言語構成に関連しています。 2番目のコードは問題を修正します。
foreach(Item i on ItemCollection)
{
Something s = new Something(i);
s.EventX += (sender, eventArgs) => { ProcessItem(eventArgs.Item);};
SomethingCollection.Add(s);
}
「Something」クラスに「i」を渡して、EventXのイベント引数で使用するだけではありませんか