web-dev-qa-db-ja.com

ラムダイベントハンドラーを削除する方法

可能な重複:
C#での匿名メソッドのサブスクライブ解除
「匿名」イベントハンドラーの登録を解除する方法

最近、ラムダを使用して単純なイベントハンドラを作成できることを発見しました。たとえば、次のようなクリックイベントをサブスクライブできます。

button.Click += (s, e) => MessageBox.Show("Woho");

しかし、どのように退会しますか?

220
Svish

C#仕様では、2つの匿名関数(匿名メソッドまたはラムダ式)がある場合、そのコードから同等のデリゲートを作成できる場合とできない場合があることを明示的に規定しています(IIRC)。 (2つのデリゲートは、ターゲットが等しく、同じメソッドを参照している場合、等しくなります。)

確かに、使用したデリゲートインスタンスを覚えておく必要があります。

EventHandler handler = (s, e) => MessageBox.Show("Woho");

button.Click += handler;
...
button.Click -= handler;

(仕様の関連部分を見つけることはできませんが、C#コンパイラが積極的に同等のデリゲートを作成しようとするのを見て驚いています。それに頼ることは確かに賢明ではありません。)

そうしたくない場合は、メソッドを抽出する必要があります。

public void ShowWoho(object sender, EventArgs e)
{
     MessageBox.Show("Woho");
}

...

button.Click += ShowWoho;
...
button.Click -= ShowWoho;

ラムダ式を使用して自分自身を削除するイベントハンドラを作成する場合は、少し注意が必要です。ラムダ式自体の内部でデリゲートを参照する必要があり、単純な「ローカル変数を宣言して割り当てる」ではできません。変数が確実に割り当てられていないためです。通常、最初に変数にヌル値を割り当てることでこれを回避できます。

EventHandler handler = null;
handler = (sender, args) =>
{
    button.Click -= handler; // Unsubscribe
    // Add your one-time-only code here
}
button.Click += handler;

残念ながら、イベントはきれいに表現されないため、これをメソッドにカプセル化するのは簡単ではありません。一番近いのは次のようなものです。

button.Click += Delegates.AutoUnsubscribe<EventHandler>((sender, args) =>
{
    // One-time code here
}, handler => button.Click -= handler);

新しいEventHandler(単なる汎用型引数)を作成する必要があるため、それでもDelegates.AutoUnsubscribe内に実装するのは難しいでしょう。実行可能ですが、面倒です。

313
Jon Skeet