ひとりでのアプリ開発 - fineの備忘録 -

ひとりでアプリ開発をするなかで起こったことや学んだことを書き溜めていきます

C# - delegate:関数を参照するための型

初めに

 C# には、delegate(デリゲート)と呼ばれる関数を参照するための型があります。本記事では、delegate の意味やその使い方についてまとめます。


delegate

delegate とは

 delegate とは、

関数を参照するための型

のこと、つまり、関数を引数に持つ型(参照型)になります。

 delegate は直訳では「委譲」を意味し、引数として代入した関数と同じ処理をする(処理を代入した関数に譲る)ようなニュアンスになります。

delegate の定義

 delegate は次のように定義します。

delegate 戻り値の型 デリゲート型名(引数リスト);

 delegate の型の変数には、delegate 定義時に指定した戻り値の型、引数の型を持つ関数を代入できます。

(例)

using UnityEngine;

public class DelegateTest : MonoBehaviour
{
    //delegate型の定義
    delegate void SampleDelegate(int a);

    void Start()
    {
        //delegate型の変数に関数を代入
       //代入する関数の戻り値, 引数はdelegate型と同じものである必要がある
        SampleDelegate del = new SampleDelegate(Method);

       //delegateを介して関数を呼び出す. 
       //Methodを代入したため, Methodが呼び出される
        del(100);
    }

    void Method(int num)
    {
        Debug.Log($"num:{num}");
    }
}

※Unityでやるために "using UnityEngine;" を書いていますが、通常の C# であれば "using System;" でよいです。

(実行結果)

C#2.0 以降は暗黙の変換が可能
 delegate型に関数を代入するには、C#1.1 までは、先ほどの例のように new が必要でした。しかし、C# 2.0 以降では、関数から delegate への暗黙の変換ができるようになり、そのまま代入できるようになりました。

//C#1.1まで
SampleDelegate del = new SampleDelegate(Method);

//C#2.0以降はこれもok
SampleDelegate del = Method;
マルチキャスト

 delegate は複数の関数を代入し、代入したすべての関数を呼び出すことができます。

 関数の追加には += 演算子を、削除には -= 演算子を使います。

(例)

using UnityEngine;

public class DelegateTest : MonoBehaviour
{
    delegate void SampleDelegate();

    void Start()
    {
        SampleDelegate del = MethodA;
        del += MethodB;  //MethodBを追加
        del += MethodC;  //MethodCを追加
        del -= MethodA;  //MethodAを削除
        del();
    }

    void MethodA()
    {
        Debug.Log($"MethodAが呼び出された");
    }

    void MethodB()
    {
        Debug.Log($"MethodBが呼び出された");
    }

    void MethodC()
    {
        Debug.Log($"MethodCが呼び出された");
    }
}

(実行結果)

(細かい仕様について)

〇 同じ関数を複数代入した場合、どうなる?
→ 代入してある個数分、その関数が呼び出されます。


〇 代入していない関数を削除した場合、どうなる?
→ その関数が代入されていない状態から変化しません。エラーも出ませんでした。

delegate の用途

 関数を作る際、条件の部分を任意にしておき、場合に合わせて条件を変更したい場合があります。そのような問題を delegate は解決してくれます。

using UnityEngine;

public class DelegateTest : MonoBehaviour
{
    delegate bool BoolDelegate(int num);

    void Start()
    {
        //delegate型に関数を代入s
        BoolDelegate del = OverFive;

        //実行
        //今回は6がOverfive関数の条件を満たせば、コンソールに表示される
        ShowConsole(6, del);
    }

    //整数numが条件を満たしたらコンソールに表示
    //条件は変更できるように delegate型にしておく
    void ShowConsole(int num, BoolDelegate a)
    {
        if (a(num))
        {
            Debug.Log($"num = {num} は条件を満たす");
        }
    }

    //引数が5より大きい場合trueを, 5以下の場合falseを返す
    bool OverFive(int x)
    {
        bool a;
        a = x > 5 ? a = true : a = false;
        return a;
    }
}
匿名メソッド式(C#2.0以降)

 C#1.1 までは、delegate を使う際には、必ずどこかで関数を定義し、その定義した関数を参照する必要がありました。

 C#2.0から導入された匿名メソッド式を使うと、delegate を代入する箇所に直接、名前のない関数を記述できるようになります。


 匿名メソッド式では、次のように delegate型への代入部分を記述します。

delegate (引数リスト){ 関数の処理 }


(例:挙動は上の「delegate の用途」のコードと同様)

using UnityEngine;

public class DelegateTest : MonoBehaviour
{
    delegate bool BoolDelegate(int num);

    void Start()
    {
        //実行
       //delegate型に関数を代入する部分が削除され, 引数の部分に delegate型に代入する関数の処理を記述するようになった
        ShowConsole(6, delegate (int x)
        {
            bool a;
            a = x > 5 ? a = true : a = false;
            return a;
        });
    }

    //整数numが条件を満たしたらコンソールに表示
    void ShowConsole(int num, BoolDelegate a)
    {
        if (a(num))
        {
            Debug.Log($"num = {num} は条件を満たす");
        }
    }
}

//関数の定義が削除された
ラムダ式C#3.0以降)

 C#3.0 以降では、ラムダ式と呼ばれる記法が使えるようになりました。この記法を用いることで、匿名関数をさらに簡潔に書くことができるようになりました。

 匿名メソッド式(C#2.0)では、次のように記述していました。

delegate (int x)
{
      bool a;
      a = x > 5 ? a = true : a = false;
      return a;
}

 ラムダ式と使うと、delegate が省略できます。

(int x) =>
{
      bool a;
      a = x > 5 ? a = true : a = false;
      return a;
}

 中括弧の中身が単体であれば、中括弧と return も省略できます。

//整数nが0より大きい場合, trueを返す
(int n) => n > 0;

 また、delegate型で引数の型を書いているなど、引数の型が文脈から明らかな場合、引数の型を省略することができます。

//delegate型の定義
//引数の型はintで確定
delegate bool BoolDelegate(int num);

//右辺に関数の処理を記述
//変数nはintであると推論してくれる
BoolDelegate del = n => n > 5;

 これまでの書き方では、delegate型の定義を記述する必要がありましたが、Func と呼ばれる delegate が標準で用意されています。これを使うと delegate 型の定義を別でする必要がなくなり、さらに簡潔に書くことができます。

Func < T,TResult >
T は関数の引数の型(引数なしの場合は書かない)
TRusult は関数の戻り値の型


(例)先ほどの delegate を Func を使って記述

//Func を使って記述
//引数がint, 戻り値がboolの関数を格納できる
Func<int, bool> f = x => { return x > 5; };

//以下, 先ほどのdelegateの記述
delegate bool BoolDelegate(int num);

BoolDelegate del = n => n > 5;

 Func も中括弧の中身が単体であれば、中括弧と return も省略できます。

Func<int, bool> f = x => x > 5;

 Func は別で定義した関数をそのまま代入することも可能です。

//Medianという関数を格納
//第一, 第二引数はMedianの引数の型, 第三引数はMedianの戻り値の型
Func<float, float, float> f = Median;

float Median(float x, float y)
{
    float med = (x + y) / 2;
    return med;
}

 ※Func はジェネリックと呼ばれる機能を使って定義されているため、 <> を使っています。
 ※ラムダ式は匿名関数を簡潔に書く以外にも、式木と呼ばれるものを作ることができる機能もあります。

最後に

  • delegate は関数を参照するための型
  • 途中で処理を変えたい場合に用いることがある
  • 匿名メソッド式やラムダ式を用いると簡潔に記述できる