i-school - デリゲート

デリゲートとは?


 デリゲート(delegate: 代理、委譲、委託)とは、メソッドを参照するための型です。
変数として宣言することで、変数内にメソッドを代入をすることが出来ます。


参考サイト
MicroSoft
デリゲート (C# プログラミング ガイド)
++C++; // 未確認飛行 C 様
デリゲート



 デリゲートは、電話でピザを注文するプロセスに例えることができます。

 1.あなたが「電話でピザ屋に注文をする(注文メソッド)」代わりに、友達に頼むことができます。
 2.友達は、あなたが指定したピザ屋に電話して注文をします。

 この例では、友達がデリゲート(代理人)の役割を果たしています。

 同様に、C#のデリゲートは、参照しているメソッドを、代わりに呼び出す役割を果たします。


デリゲートの定義


 デリゲートは、次のように定義されます。

アクセス修飾子 delegate 戻り値の型 デリゲート型名(引数リスト);

 この定義を元に、自由にデリゲートの定義を行うことで作成が出来ます。

<例>
public delegate void MyDelegate();

 これは、戻り値がvoidで、引数がないメソッドを参照するデリゲートMyDelegateを定義しています。



デリゲートの使用


 定義したデリゲートは、クラスや構造体と同じように、1つの“型”として扱われます。

 先ほどのMyDelegate型のインスタンスを作成した場合、戻り値がvoid で、引数がないメソッドを代入することが出来ます。



 デリゲートを使用するには、まず対応する(代入する)メソッドを定義します。


public void PrintHello()
{
    Console.WriteLine("Hello!");
}

 このメソッドも戻り値は void で、引数はありません。
そのため、MyDelegate型の定義と同じであるため、MyDelegate型に代入できるメソッドになります。



 次に、デリゲートのインスタンスを作成し、メソッドをデリゲートに割り当てます(代入します)。
new は不要で、変数に対してメソッドを代入する処理を記述します。

MyDelegate myDelegate = PrintHello;

 代入するメソッドは引数はない状態で、メソッド名のみを記述します。



 最後に、デリゲートを実行して、割り当てられた(代入された)メソッドを呼び出します。


myDelegate();

 デリゲートの型に () を実行することでメソッドが呼び出されます。

 この例では、myDelegate デリゲートを実行することで、参照している(代入されている) PrintHelloメソッドを実行し、"Hello!"と出力します。



 それでは上記の説明を元に、サンプルコードを提示します。


using UnityEngine;

public class DelegateExample : MonoBehaviour
{
    // デリゲートの定義
    public delegate void MyDelegate();


    void Start()
    {
        // デリゲートにメソッドを割り当てる
        MyDelegate myDelegate = PrintHello;

        // デリゲートを実行する
        myDelegate();
    }

    // デリゲートに割り当てるメソッド
    void PrintHello()
    {
        Debug.Log("Hello!");
    }
}

 先ほどの説明とソースコードとの確認しながら、どのような機能としてデリゲートの処理が動いているのかをイメージしていきましょう。


デリゲートとイベント


 デリゲートは、イベントのハンドラとしても使用されます。
例えば、ボタンクリックイベントにデリゲートを使用して、クリックされたときに特定のメソッドを呼び出すことができます。



 最初にイベントハンドライベントリスナについて簡単に説明します。

 イベントハンドラとは、特定のイベントが発生したときに実行されるメソッドのことを指します。
例えば、ボタンがクリックされたときに実行する処理を書いたメソッドは、ボタンのクリックイベントのハンドラとなります。

 イベントが発生したときに、それに対応するメソッド(イベントハンドラ)を実行するためには、そのメソッドへの参照が必要です。
デリゲートはまさにそのためのもので、メソッドへの参照を保持し、必要なときにそのメソッドを呼び出します

 したがって、デリゲートはイベント駆動型プログラミングにおいて非常に重要な役割を果たします。
これにより、イベントが発生したときに特定のアクションを動的に割り当てたり、変更したりすることが可能となります。



 イベントリスナとは、特定のイベントを監視して、そのイベントが発生したときにイベントハンドラを呼び出す仕組みのことを指します。
リスナは「リスニング」つまり「聞き耳を立てている」ことから名付けられています。
リスナはイベントを「聞いて」、そのイベントが発生したときに適切なハンドラを呼び出します。

 例えばボタンのイベントであれば、「ボタンを押す」という処理を特定のイベントとみなし、それを監視する役目がイベントリスナです。

 ボタンの場合には、AddListener メソッドが用意されており、この引数にはデリゲートとしてメソッドが登録できるようになっています。
これがイベントリスナとして機能します。



 それでは、ボタンを使ったイベントの簡単な実装例と挙動を確認するサンプルコードを提示します。


using UnityEngine;
using UnityEngine.UI;

public class ButtonExample : MonoBehaviour
{
    public Button button;

    private void Start()
    {
        button.onClick.AddListener(OnButtonClick); // イベントリスナにあたる、「ボタンを押したら」という特定のイベントを監視する処理
    }

    private void OnButtonClick()  // ← ボタンのイベントのイベントハンドラにあたる、デリゲートして利用するメソッド
    {
        Debug.Log("ボタンがクリックされました。");
    }
}
 
 このスクリプトでは、ButtonクラスのonClickイベントにOnButtonClickメソッドを AddListener メソッドを使って、イベントリスナとして追加しています。
これはデリゲートの処理になっており、AddListener メソッドは、OnButtonClickメソッドへの参照を保持しています。
そして「ボタンがクリックされたとき」のイベントをリスナが監視しています。

 ボタンがクリックされると、イベントハンドラである、AddListener メソッドのデリゲートが参照している OnButtonClickメソッドが呼び出され、
Debug.Log メソッドが実行されてメッセージが表示されます。


 
 このように、デリゲートとイベントを使用して、ボタンのクリックイベントに対して処理を実行しています。

 イベントリスナとイベントハンドラは一緒に動作して、特定のイベントが発生したときに特定のコードが実行されるようにします。
これはプログラムのフローを管理し、特定のイベントに対して柔軟に対応するための重要な仕組みです。


デリゲートの種類


 C# には、さまざまなデリゲートが用意されており、それぞれ特定の目的やシナリオに適した形で使われます。以下は、C#で一般的に使われるデリゲートのいくつかの例です。


Funcデリゲート


 Funcデリゲートは、戻り値を持つメソッドを表します。Funcデリゲートは、0〜16個の引数を持つことができます。最後の型引数は、戻り値の型です。

Func<T>: 引数をとらず、T型の戻り値を返すデリゲートです。
Func<T1, T2>: T1型の引数を1つとり、T2型の戻り値を返すデリゲートです。
Func<T1, T2, TResult>: T1型とT2型の引数をそれぞれ1つずつとり、TResult型の戻り値を返すデリゲートです。

// 引数がなく、int型の戻り値を持つメソッドを表すデリゲート
Func<int> getInt;

// int型の引数を1つとり、bool型の戻り値を持つメソッドを表すデリゲート
Func<int, bool> isEven;

int GetRandomNumber()
{
    return new System.Random().Next(1, 100);
}

bool CheckIfEven(int number)
{
    return number % 2 == 0;
}

void Start()
{
    getInt = GetRandomNumber;
    isEven = CheckIfEven;

    int randomNumber = getInt();
    Debug.Log($"生成された数値: {randomNumber}");
    Debug.Log($"偶数ですか?: {isEven(randomNumber)}");
}


Actionデリゲート


 Actionデリゲートは、戻り値がないメソッド(void)を表します。Actionデリゲートは、0〜16個の引数を持つことができます。


Action: 引数をとらず、戻り値がないデリゲートです。void型のメソッドを表します。
Action<T>: T型の引数を1つとり、戻り値がないデリゲートです。
Action<T1, T2>: T1型とT2型の引数をそれぞれ1つずつとり、戻り値がないデリゲートです。


// 引数がなく、戻り値がvoidのメソッドを表すデリゲート
Action showMessage;

// int型の引数を1つとり、戻り値がvoidのメソッドを表すデリゲート
Action<int> displayNumber;

void ShowMessage()
{
    Debug.Log("Hello, World!");
}

void DisplayNumber(int number)
{
    Debug.Log($"表示する数値: {number}");
}

void Start()
{
    showMessage = ShowMessage;
    displayNumber = DisplayNumber;

    showMessage();
    displayNumber(42);
}


Predicate デリゲート


 Predicate<T>: T型の引数を1つとり、bool型の戻り値を返すデリゲートです。述語として使われます。



Predicate 型とは?


 「Predicate」とは、一般的に述語と呼ばれるもので、プログラミングにおいては、ある条件を満たすかどうかを判断する関数やメソッドを指します
よって述語は、論理値(真偽値、つまりtrueまたはfalse)を返すことが特徴です。

 C#では、Predicate<T>型のデリゲートが述語の役割を果たします。

 このデリゲートは、指定された型Tのオブジェクトを引数に取り、そのオブジェクトが特定の条件に一致するかどうかを判断するために使用されます。
Predicate<T>型のデリゲートは、bool型の戻り値を持ちます。これは、述語がある条件を満たすかどうかを判断する役割を果たすため、trueまたはfalseを返す必要があるからです。

 そのため Predicate<T>型のデリゲート(述語)とは、 bool 型の戻り値を持つデリゲートのテンプレートとして考えても問題ありません。



 Predicate<T>デリゲートの定義は次のようになります。


public delegate bool Predicate<in T>(T obj);

 Predicate<T>型のデリゲートは、主にコレクションやリストに対してフィルタリング処理を行う際に使用されます。
例えば、List<T>クラスのFindAllメソッドでは、Predicate<T>型のデリゲートを引数に取り、条件に一致する要素だけを抽出した新しいリストを生成します。



 以下の例では、List<int>から偶数の要素だけを抽出しています。


List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

Predicate<int> isEven = n => n % 2 == 0;

List<int> evenNumbers = numbers.FindAll(isEven);

 この場合、isEven の Predicate はラムダ式で記述されており、int型の引数 n を受け取り、その値が偶数であればtrue、奇数であればfalseを返します。
その後、FindAllメソッドの引数として渡されています。
これにより、numbersリストから偶数の要素だけが抽出された新しいリストevenNumbersが生成されます。



 これらのデリゲートは、様々なシナリオで使用されます。
例えば、LINQクエリ、イベントハンドラ、非同期処理、データ検証などで活用されています。

 また、独自のデリゲート(カスタム・デリゲート)を作成することも可能です。


UnityAction と UnityEvent


 UnityではUnityActionというActionに似たデリゲートが提供されています。
UnityActionは、特に引数なしのイベントハンドラに使用されます。
戻り値がvoidであるため、イベントに関連付けられたコールバックメソッドが何も返さないことが保証されます。



 一方で、UnityEventはデリゲートではなく、イベントをカプセル化するクラスです。
UnityEventは、System名前空間のEventクラスとは異なり、UnityEngine.Events名前空間に属します。

 UnityEventは、UnityActionデリゲートをイベントハンドラとして使用し、イベントが発生したときに、登録されたUnityActionメソッドを呼び出します。

 UnityEventは、独自のイベントを定義し、Unityのインスペクタ上ーで簡単にイベントハンドラ(メソッド)を割り当てることができます。
また、UnityEventは、パラメータ付きのUnityEvent<T>やUnityEvent<T0, T1>など、複数の引数をサポートするバリエーションも提供しています。
これらのバリエーションは、それぞれ対応するUnityAction<T>やUnityAction<T0, T1>デリゲートをイベントハンドラとして使用します。


 
 以上になります。

 次は デリゲートとラムダ式の関係 です。