Delegate と Event [C#]

C#のデリゲートとイベントを整理する.

デリゲート(Delegate)

「代表」という意味がふさわしい.
ネットではdelegationで委譲と呼ばれている感じがあるけれど,意味が界隈によってつかみにくい.

ja.wikipedia.org

主にC#Visual Basic .NETなどの、.NET Frameworkプログラミング言語に用意されている機能であり、参照型の一種(デリゲート型)である。

System.Delegateから派生した参照型で,オブジェクト参照に似ているが,メソッドを指しているのが違い.
型変換の危険がないため,type safeな関数ポインタと呼ばれることがある.

// 事前にMydelをデリゲートとして定義
public delegate int Mydel(int x, int y);

// Sum メソッド定義
public static int Sum(int a, int b) 
{
   return a + b;
}

// Sum メソッドを指すようにデリゲート宣言
Mydel del = new MyDel(Sum);

// 呼び出すとき
del.Invoke(a, b);
// del(a, b)でもよいが,個人的にはInvoke使う方が好き

Javaでは…

厳密には意味は異なるのですが,Javaで言うSAMに似ている感じがあります.

qiita.com

matarillo.hatenadiary.jp

xrdnk.hateblo.jp

ラムダ式とデリゲート

こちらの記事を読めば完璧.
qiita.com

結局Javaと似てる.

共変性(Co-variance)と反変性(Contra-variance)

デリゲートのインスタンス化の時に「本来指定されていた戻り値の型」よりも,
「継承階層の下方にある型」を戻り値とするメソッドの割り当てができることを共変性という.

また,デリゲート型よりも継承階層の上方にある型をパラメタとしてメソッドが出来ることを反変性という.

docs.microsoft.com

今回では詳しく取り上げない.

マルチキャストデリゲート(Multicast Delegate)

デリゲートを使って,シグニチャが同じ複数のメソッドをカプセル化すること.

using System;
namespace MulticastDelegate {
    public delegate void MultiDel ();
    class Program {
        public static void show1 () { Console.WriteLine ("Program.Show1()"); }
        public static void show2 () { Console.WriteLine ("Program.Show2()"); }
        public static void show3 () { Console.WriteLine ("Program.Show3()"); }
        static void Main (string[] args) {
            Console.WriteLine ("*** マルチキャストデリゲートの例***");
            MultiDel md = new MultiDel (show1);
            md += show2; // この書き方が出来る
            md += show3; 
            md ();
            Console.ReadKey ();
        }
    }
}

結果

*** マルチキャストデリゲートの例***
Program.Show1()
Program.Show2()
Program.Show3()

マルチキャストのデリゲート型はvoidの戻り型が最適.

イベント (Event)

オブジェクトの状態が変化したことを通知したり,その時にシグナルが発生したりするときに使われる.
GUIアプリケーションにおいて,よく出てくる概念である.
Observerパターンに利用されるし,UnityではUnityEventがあったり,Eventの上位互換UniRxがあったりする.

xrdnk.hateblo.jp

xrdnk.hateblo.jp

xrdnk.hateblo.jp

Event について覚えるべきこと

  • イベントはデリゲートに関連付けられる.
  • .NETでは,イベントはマルチキャストデリゲートとして実装される.
  • イベントはPub/Subモデルに従う.
    Publisherが通知を送り,Subscriberが通知を受け取る.
    Subscriberはいつでも情報の待ち受けを開始や中止が出来る.
  • Publisherはデリゲートを含むデータ型で,Subscriberは,Publisherのデリゲートに
    +=演算子を使って自分自身を登録し,そのデリゲートに-=演算子を使って登録解除が出来る.
    マルチキャストデリゲートなのを思い出そう)
  • Subscriber同士の通信はない.【重要】
    つまり
    • Subscriber同士の通信は許されない
    • 疎結合システムを構築できる状態にある【最高!】
  • .NET Frameworkでは,標準イベントデザインパターンに対応するジェネリック型デリゲートが使える
    public delegate void EventHandler\<TEventArgs>(object sendersource, TEventArgs e) where TEventArgs : EventArgs;

C#で簡単なイベントを実装する手順

  1. Publisher クラスの作成
    1. デリゲートを1つ作成する.
    2. デリゲートに基づいてイベントを作成する.
    3. イベントを発火させる.
  2. Subscriber クラスの作成
    1. イベント処理メソッド(イベントハンドラ)を書く.
using System;
namespace EventEx {
    // Publisher クラスの作成
    class Publisher {
        // 1.1 デリゲートを作成する
        /* デリゲートの名前は
         * 「実装しようとしているイベントの名前」+ EventHandler
         * という形にする*/
        public delegate void JobDoneEventHandler (object sender, EventArgs args);
        // 1.2 デリゲートに基づいてイベントを作成する
        public event JobDoneEventHandler JobDone;
        public void ProcessOneJob () {
            Console.WriteLine ("Publisher:ジョブを1 つこなします");
            // 1.3 イベントを発生させる
            OnJobDone ();
        }
        /* 標準の形式では、メソッドをprotected virtual でタグ付け。
         * また、メソッド名をイベントの名前と揃えて、先頭にOn を付ける*/
        protected virtual void OnJobDone () {
            if (JobDone != null)
                JobDone (this, EventArgs.Empty);
        }
    }
    // ステップ2:Subscriber クラスの作成
    class Subscriber {
        // イベント処理
        public void OnJobDoneEventHandler (object sender, EventArgs args) {
            Console.WriteLine ("購読者が通知を受けました");
        }
    }
    class Program {
        static void Main (string[] args) {
            Console.WriteLine ("***簡単なイベントのデモ***");
            Publisher sender = new Publisher ();
            Subscriber receiver = new Subscriber ();
            sender.JobDone += receiver.OnJobDoneEventHandler;
            sender.ProcessOneJob ();
            Console.ReadKey ();
        }
    }
}