50

C# のイベント宣言の恐ろしさはよく知られています。スレッドセーフを確保するために、標準では次のように記述します

public event EventHandler SomethingHappened;
protected virtual void OnSomethingHappened(EventArgs e)
{            
    var handler = SomethingHappened;
    if (handler != null)
        handler(this, e);
}

最近、このボードの他の質問 (今は見つかりません) で、このシナリオでは拡張メソッドをうまく使用できると誰かが指摘しました。これを行う1つの方法は次のとおりです。

static public class EventExtensions
{
    static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e)
    {
        var handler = @event;
        if (handler != null)
            handler(sender, e);
    }
    static public void RaiseEvent<T>(this EventHandler<T> @event, object sender, T e)
        where T : EventArgs
    {
        var handler = @event;
        if (handler != null)
            handler(sender, e);
    }
}

これらの拡張メソッドを配置すると、イベントを宣言して発生させるために必要なのは、次のようなものだけです。

public event EventHandler SomethingHappened;

void SomeMethod()
{
    this.SomethingHappened.RaiseEvent(this, EventArgs.Empty);
}

私の質問: これは良い考えですか? 標準の On メソッドがないことで何か不足しているのでしょうか? (私が気づいたことの 1 つは、明示的な追加/削除コードを持つイベントでは機能しないことです。)

4

6 に答える 6

59

明示的な追加/削除があるイベントでも引き続き機能します。イベント名の代わりにデリゲート変数 (またはデリゲートを保存したもの) を使用するだけで済みます。

ただし、スレッドセーフにする簡単な方法があります - no-op ハンドラで初期化します:

public event EventHandler SomethingHappened = delegate {};

追加のデリゲートを呼び出すことによるパフォーマンスへの影響はごくわずかであり、コードが簡単になることは間違いありません。

ところで、拡張メソッドでは、追加のローカル変数は必要ありません。次のようにするだけです。

static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e)
{
    if (@event != null)
        @event(sender, e);
}

static public void RaiseEvent<T>(this EventHandler<T> @event, object sender, T e)
    where T : EventArgs
{
    if (@event != null)
        @event(sender, e);
}

個人的には、キーワードをパラメーター名として使用しませんが、実際には呼び出し側をまったく変更しないため、必要なことを行ってください:)

編集:「OnXXX」メソッドについて:クラスの派生元を計画していますか? 私の見解では、ほとんどのクラスは封印されるべきです。その場合、それらの派生クラスがイベントを発生できるようにしますか? これらの質問のいずれかに対する答えが「いいえ」の場合は、気にしないでください。両方の答えが「はい」の場合は、実行してください:)

于 2008-10-23T21:09:04.513 に答える
15

C# 6 が登場しました。イベントを発生させるための、よりコンパクトでスレッド セーフな方法があります。

SomethingHappened?.Invoke(this, e);

Invoke()null 条件演算子 "?" のおかげで、デリゲートがイベントに登録されている (つまり、null でない) 場合にのみ呼び出されます。

質問の「ハンドラー」コードが解決しようとしているスレッドの問題は、そのコードのようにSomethingHappened一度しかアクセスされないため、ここでは回避されているため、テストと呼び出しの間で null に設定される可能性はありません。

この回答はおそらく元の質問に接するものですが、イベントを発生させるためのより簡単な方法を探している人にとっては非常に関連があります。

于 2015-08-14T17:03:49.477 に答える
5

【ここから感想です】

推奨される方法でコードを 1 回記述するだけで完了です。そうすれば、コードを見ている同僚が何か間違ったことをしたと思って混乱することはありませんか?

[これまでイベント ハンドラーの作成に費やすよりも、イベント ハンドラーを作成する方法を見つけようとしている投稿をより多く読んでいます。]

于 2008-10-23T21:49:35.260 に答える
3

コードが少なくなり、読みやすくなります。私のように。

パフォーマンスに関心がない場合は、次のようにイベントを宣言して、null チェックを回避できます。

public event EventHandler SomethingHappened = delegate{};
于 2008-10-23T21:11:15.993 に答える
1

ハンドラーをローカル変数に割り当てることで、スレッドの安全性を「確保」していません。代入後もメソッドが中断される可能性があります。たとえば、イベントをリッスンしていたクラスが中断中に破棄された場合、破棄されたクラスのメソッドを呼び出しています。

null 参照例外から身を守っていますが、Jon Skeet と cristianlibardo が回答で指摘したように、それを行う簡単な方法があります。

もう 1 つのことは、封印されていないクラスの場合、OnFoo メソッドは拡張メソッドでは可能だとは思わない仮想でなければならないということです。

于 2008-10-23T21:16:52.113 に答える