デリゲート型の変数に関数を複数登録する

プログラミングC# 第6版を読んでいて気付いたんですが、「+=」での関数登録は何もイベントだけの特殊機能ではないんですね。

その辺詳しく書いてなかったので今まで勘違いしていました。

using System;

delegate void CDelegate ();

class Program {
    static void Main () {
        CDelegate dobj;
        
        dobj  = () => Console.WriteLine("hoge1");
        dobj += () => Console.WriteLine("hoge2");
        
        dobj();
    }
}
$ main
hoge1
hoge2

この機能はデリゲート型の機能だったんですね。ではeventキーワードを付けた場合と一体何が違うんでしょうか?

これについて調べてみました。eventをつけると、そのクラス内でのみデリゲート型の変数を関数として呼び出せるということみたいです。

「+=」や「-=」はクラス外部からでも関数登録をさせたいんですが、関数呼び出しはクラス外部からされると微妙なのでそれをeventキーワードで防ぐということだったみたいです。

つまりもしeventを付けなかった場合、以下のようになってしまいます。

using System;

class CClass {
    // eventを付けない
    public EventHandler Begin;
    public void Proc () {
        if ( Begin != null ) { Begin(this,EventArgs.Empty); }
    }
}

class Program {
    static void Main () {
        CClass cobj = new CClass();
        
        cobj.Begin += (sender,e) => Console.WriteLine("Begin1");
        cobj.Begin += (sender,e) => Console.WriteLine("Begin2");
        
        // Procの中でやりたいイベントなのに、無理やり呼べてしまう!
        cobj.Begin(cobj,EventArgs.Empty);
        
        cobj.Proc();
    }
}
$ main
Begin1
Begin2
Begin1
Begin2

もちろんBeginフィールドをprivateにしてしまえば外部から関数呼び出しされることはありませんが、その代わり「+=」等も使えなくなるのでわざわざアクセス用のメソッドを追加しないといけなくなります。

using System;

class CClass {
    // privateにする
    private EventHandler Begin;
    
    // Beginへの処理を行うメソッドが必要になる
    public void AddBegin (EventHandler e) {
        Begin += e;
    }
    public void RemoveBegin (EventHandler e) {
        Begin -= e;
    }
    
    public void Proc () {
        if ( Begin != null ) { Begin(this,EventArgs.Empty); }
    }
}

class Program {
    static void Main () {
        CClass cobj = new CClass();
        
        // アクセス用メソッド経由でイベント登録
        cobj.AddBegin( (sender,e) => Console.WriteLine("Begin1") );
        cobj.AddBegin( (sender,e) => Console.WriteLine("Begin2") );
        
        cobj.Proc();
    }
}

これは非常に面倒ですね。

そこでeventキーワードが役に立つというわけです。

using System;

class CClass {
    // eventキーワードを付ける
    public event EventHandler Begin;
    public void Proc () {
        if ( Begin != null ) { Begin(this,EventArgs.Empty); }
    }
}

class Program {
    static void Main () {
        CClass cobj = new CClass();
        
        cobj.Begin += (sender,e) => Console.WriteLine("Begin1");
        cobj.Begin += (sender,e) => Console.WriteLine("Begin2");
        
        // eventキーワードが付いているので関数呼び出しはできない!
        cobj.Begin(cobj,EventArgs.Empty);
        
        cobj.Proc();
    }
}
$ csc main.cs
Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.1
Copyright (C) Microsoft Corporation. All rights reserved.

main.cs(19,8): error CS0070: イベント 'CClass.Begin' は +=、-= の左辺にのみ使用できます。ただし、'CClass' 型内から使用されている場合を除きます。

というわけで、デリゲート型に複数の関数が登録できるということや、eventキーワードについてちゃんと理解できたと思います。

どうもオライリーの本は初心者向けという感じではないですねぇ。初めに勉強する本としてはちょっとレベルが高かったかもです。