web-dev-qa-db-ja.com

空のC#イベントハンドラーを自動的に作成する

ハンドラーがアタッチされていないC#でイベントを発生させることはできません。そのため、各呼び出しの前に、イベントがnullかどうかを確認する必要があります。

if ( MyEvent != null ) {
  MyEvent( param1, param2 );
}

コードをできるだけきれいに保ち、これらのnullチェックを削除したいと思います。少なくとも私の場合はそうではありませんが、パフォーマンスに大きな影響はないと思います。

MyEvent( param1, param2 );

現時点では、各イベントに空のインラインハンドラーを手動で追加することでこれを解決しています。私はそれを行うなどのことを覚えておく必要があるため、これはエラーを起こしやすいです。

void Initialize() {
  MyEvent += new MyEvent( (p1,p2) => { } );
}

リフレクションといくつかのCLRマジックを使用して、特定のクラスのすべてのイベントに対して空のハンドラーを自動的に生成する方法はありますか?

68
Tomas Andrle

私は別の投稿でこれを見て、恥知らずにそれを盗み、それ以来私のコードの多くでそれを使用しました:

public delegate void MyClickHandler(object sender, string myValue);
public event MyClickHandler Click = delegate {}; // add empty delegate!

//Let you do this:
public void DoSomething() {
    Click(this, "foo");
}

//Instead of this:
public void DoSomething() {
    if (Click != null) // Unnecessary!
        Click(this, "foo");
}

*誰もがこの手法の起源を知っている場合は、コメントに投稿してください。ソースが正当な信用を得ることを本当に信じています。

編集:この投稿から得た C#の隠し機能?

143
Dinah

表記法:

if ( MyEvent != null ) {
  MyEvent( param1, param2 );
}

スレッドセーフではありません。次のようにしてください:

EventHandler handler = this.MyEvent;
if ( null != handler ) { handler( param1, param2 ); }

これはわずらわしいので、ヘルパーメソッドを実行できます。

static void RaiseEvent( EventHandler handler, object sender, EventArgs e ) {
    if ( null != handler ) { handler( sender, e ); }
}

次に呼び出します:

RaiseEvent( MyEvent, param1, param2 );

C#3.0を使用している場合、ヘルパーメソッドを拡張メソッドとして宣言できます。

static void Raise( this EventHandler handler, object sender, EventArgs e ) {
    if ( null != handler ) { handler( sender, e ); }
}

次に呼び出します:

MyEvent.Raise( param1, param2 );

また、他のイベントハンドラーの次の拡張機能/ヘルパーメソッドを作成できます。例えば:

static void Raise<TEventArgs>( this EventHandler<TEventArgs> handler,
    object sender, TEventArgs e ) where TEventArgs : EventArgs
{
    if ( null != handler ) { handler( sender, e ); }
}
58
TcKs

C#6.0では、条件付きnull演算子_?._のおかげで、これらの長さに移動してnullチェックを行う必要はありません。

ドキュメントMyEvent?.Invoke(...)を呼び出すと、イベントが一時変数にコピーされ、nullチェックが実行され、nullでない場合は、一時コピーでInvokeが呼び出されることを説明します。誰かが一時変数へのコピーの後に新しいイベントを追加した可能性があるため、これは必ずしもあらゆる意味でスレッドセーフではありません。ただし、nullでInvokeを呼び出さないことが保証されます。

要するに:

_public delegate void MyClickHandler(object sender, string myValue);
public event MyClickHandler Click;

public void DoSomething() {
    Click?.Invoke(this, "foo");
}
_
7
gandaliter

次のように書くことができます:

MyEvent += delegate { };

あなたが何をしたいのかはわかりません。

6
leppie

さまざまなイベントハンドラにいくつかの拡張メソッドは必要ありません。必要なのは1つだけです。

public static class EventHandlerExtensions {
  public static void Raise<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs {
    if (handler != null) handler(sender, args);
  }
}
5
vkelman

これは、イベントを消費しているコードが、イベントを持つオブジェクトがデフォルトでアクションでコーディングされていることを期待しているという点で、悪い考えです。あなたのコードが他の誰かによって他の場所で決して使用されないなら、私はあなたがそれで逃げることができると思います。

2
mcintyre321

残念ながら、C#イベント宣言には、多くの既知の安全性の問題と非効率性が含まれています。 デリゲートを安全に呼び出すため、およびスレッドセーフな方法でデリゲートを登録/登録解除するために、デリゲートで多くの拡張メソッドを設計しました

古いイベント発生コード:

if (someDelegate != null) someDelegate(x, y, z);

新しいコード:

someDelegate.Raise(x, y, z);

古いイベント登録コード:

event Action fooEvent;
...
lock (someDummyObject) fooEvent += newHandler;

新しいコード:

Action fooEvent;
...
Events.Add(ref fooEvent, newHandler);

ロックは不要であり、イベントをロックするためにコンパイラーが挿入したダミーオブジェクトはありません。

1
naasking