C# の event と delegate の違い

マウスをクリックした場合などに発生する"イベント"の話ではなく、c#の予約語の"event構文"の話です。

f:id:Takachan:20160721155544p:plain

本当にざっくりとした先に結論を書くと、"event構文"は"delegate専用のプロパティ"の一種です。

delegate型をクラス外に公開する場合だけ特別に、"プロパティ構文で公開する"と"event構文"で公開する2種類が選べます。

以下のように自動実装プロパティのように宣言できます。

public class DelegateEvent
{
    // 普通のプロパティで公開する(自動実装)
    public Action<object, EventArgs> PropDelegate { get; set; }

    // event構文で公開する(自動実装)
    public event Action<object, EventArgs> HogeEvent;
}

普通のプロパティで公開した場合デリゲートに対して全ての操作ができるのですが、event構文を使用して公開した場合、利用者からはメソッドの登録と登録解除しかできなくなります。(自クラス内だとデリゲートのように使用できます)

public void Hoge(DelegateEvent source)
{
    // デリゲートのプロパティの操作

    // 自分のメソッドを設定
    source.PropDelegate = this.AnHandler;
    // 自分のメソッドを追加
    source.PropDelegate += this.AnHandler;
    // 登録内容を全部削除
    source.PropDelegate = null;
    // デリゲートを呼び出す(この場合は例外)    
    source.PropDelegate(this, new EventArgs);

    // イベントの操作

    // 自分のメソッドを設定
    source.HogeEvent= this.AnHandler; // ★「できない
    // 自分のメソッドを追加
    source.HogeEvent+= this.AnHandler;
    // 登録内容を全部削除
    source.HogeEvent= null; // ★できない
    // デリゲートを呼び出す(この場合は例外)    
    source.HogeEvent(this, new EventArgs); // ★できない
}

// デリゲートやイベントに設定するメソッド
public void AnHandler(object sender, EventArgs e) { ... }

なので、メソッドの登録解除しかさせたくない場合(オブザーバーパターン的に利用したい場合)event構文を付け加えてデリゲートを宣言します。

重要なのは、event構文を用いた場合、設定元から source.HogeEvent(this, new EventArgs); のようにメソッド呼び出しできなくなり、呼び出せるのは設定したクラス内からのみとなります。

なので「メソッドを運搬したい」や、「ある処理の述語に使用したい」、「デリゲートの終了を待って後続のシーケンスのが継続する」場合普通のプロパティで大丈夫です。

逆に通知しっぱなしの場合、eventでいいとも言えます。

自動実装じゃない場合、以下のように宣言します。

public class DelegateEvent
{
    // 普通のプロパティで公開する
    private Action<object, EventArgs> _PropDelegate
    public Action<object, EventArgs> PropDelegate
    {
        get { return this._PropDelegate; }
        set { this._PropDelegate = value; }
    }

    // event構文で公開する
    public Action<object, EventArgs> _HogeEvent; // ただのデリゲート宣言
    public event Action<sender, MyEventArgs> HogeEvent
    {
        add { this._HogeEvent+= value; }
        remove { this._HogeEvent-= value; }
        // +=, -=でメソッドを複数登録できるようにしておく(お約束)
    }
}

「オブザーバーパターン」の説明はGoFのデザインパターンを説明しているサイトがいっぱいあるのでそちらを参照してもらって使い分け方はざっくりと

1. 呼んでほしいメソッドの登録、登録解除しか{させたくない|したくない}場合event構文を使う
2. そうではなくもっといろいろしたい(設定したクラス外からも使う)場合プロパティを使う

かと思います。

補足ですが、event構文を単純に抜いただけだとフィールドを直接公開するだけになってしまうので、eventをとった時は後ろに{ get; set; }を付けるようにしましょう。

// これはよくない

// イベント構文をつけないでデリゲートをそのまま公開
//  => フィールド直接公開になっちゃう
public Action<object, EventArgs> HogeEvent;