🚀

デリゲート型(Delegate型)ってなんだよ(例:C#)

2023/03/07に公開

目次

デリゲート型(Delegate型)とは

メソッドを代入できる型である。

参照型に属する抽象クラスである。
抽象クラスは abstract class キーワードで宣言されている。
抽象クラスはインスタンス化できない。
よって実際にインスタンス化して使うのが具象クラス。
具象クラスは抽象クラスであるDelegate型を継承している。

下図の右側が Delegate型 のまとめである。

他の型と同様、自作の型も作れるし、.NETにすでに定義されている型もある。

各Delegate型の具象クラスの解説

ChatGPTさんから引用

デリゲート型の種類を10種類紹介してください

C#のデリゲート型には多くの種類がありますが、以下によく使われる10種類を紹介します。

Action 型
引数を持たずに戻り値のないメソッドを表すデリゲート型。

Action<T> 型
引数が1つで戻り値がないメソッドを表すデリゲート型。引数の型はTで指定される。

Action<T1, T2> 型
引数が2つで戻り値がないメソッドを表すデリゲート型。引数の型はT1とT2で指定される。

Func<TResult> 型
引数を持たずに戻り値があるメソッドを表すデリゲート型。戻り値の型はTResultで指定される。

Func<T, TResult> 型
引数が1つで戻り値があるメソッドを表すデリゲート型。引数の型はTで指定され、戻り値の型はTResultで指定される。

Func<T1, T2, TResult> 型
引数が2つで戻り値があるメソッドを表すデリゲート型。引数の型はT1とT2で指定され、戻り値の型はTResultで指定される。

Predicate<T> 型
1つの引数を持ち、bool型の値を返すメソッドを表すデリゲート型。

Comparison<T> 型
2つの引数を持ち、1つ目の引数が2つ目の引数よりも小さい場合は負の整数、同じ場合は0、大きい場合は正の整数を返すメソッドを表すデリゲート型。

EventHandler 型
引数が2つで、最初の引数がイベントの送信元、2つ目の引数がイベントの引数となるイベントハンドラを表すデリゲート型。

EventHandler<TEventArgs> 型
引数が2つで、最初の引数がイベントの送信元、2つ目の引数がイベントの引数となるイベントハンドラを表すデリゲート型。2つ目の引数の型はTEventArgsで指定される。

各種の型は、メソッドを代入できるデリゲート型の一種である。
それぞれの型は、代入するメソッドの種類によって変わる。
戻り値があるのか、ないのか、boolなのか、
引数があるのか、ないのか、いくつなのか、引数の型に指定があるのか、
など。

.NETに定義されているデリゲート以外を利用したい場合は、自分で定義する。

デリゲートの定義と使い方サンプル

//具象クラスの自作デリゲートの定義
// 文字列を引数に持つメソッドを入れるデリゲート
private delegate void DelegateSample(string _w); 

static void Main(string[] args)
{
    string word = "hello";

    DelegateSample myFunc; //デリゲートの宣言(インスタンス化)
    myFunc = SayWord; // デリゲートへの代入
    myFunc += SayWordWord; // デリゲートへのさらに代入

    // デリゲート型(myFunc型)のインスタンスを実行(代入されているメソッドを実行する)
    myFunc(word); 

    // 結果
    // hello
    // hellohello
    // とコンソールに出力される

    Console.ReadLine(); 
}

private static void SayWord(string word)
{
    Console.WriteLine(word);
}

private static void SayWordWord(string word)
{
    word = word + word;
    Console.WriteLine(word);
}
	

このように、デリゲート型の変数の中にメソッドを入れることができる。
メソッドを2つ以上入れることもできる。

デリゲートを発火させた時には、その中に代入されているメソッドがすべて発火する。
DelegateSample型のデリゲートは、引数が文字列で戻り値がないメソッドのみが入る。

匿名メソッドを利用したデリゲートの利用サンプル

匿名メソッドとは定義されていないメソッドのことである。
定義されていないメソッド=名前がないメソッド=匿名メソッド。
下記のサンプルコードのCount()メソッドの第二引数の部分。

    //デリゲートメソッドでtrueになったものの数を数えるメソッド
    //Predicate<int>は、int型を引数にしてbool型を返すデリゲート
    public int Count(int[] numbers, Predicate<int> judge)
    {
      int count = 0;
      foreach (var n in numbers)
      {
        if (judge(n) == true)
          count++;
      }
      return count;
    }
    
   
    public void Do()
    {
      var numbers = new[] { 5, 3, 9, 6, 7, 5, 8, 1, 0, 5, 10, 4 };
      //引数がintでそのintが偶数だったときtrueを返すメソッドを第二引数にしている
      var count = Count(numbers, delegate (int n) { return n % 2 == 0; });
      Console.WriteLine(count);
    }

Count()メソッドの第二引数がPredicate<int>型のデリゲートjudge。
このCount()メソッドの中でデリゲートjudgeが発火している。
デリゲートを引数にするときに、メソッドを入れるのではなく匿名メソッドを入れる書き方。

ラムダ式を利用したデリゲートの利用サンプル

実際には匿名メソッドではなく、ラムダ式を利用する(C#3.0以降)

匿名メソッド利用のCountメソッド

var count = Count(numbers, delegate (int n) { return n % 2 == 0; });

ラムダ式利用のCountメソッド

var count = Count(numbers, n => n % 2 == 0);

両者で表現されているものは同じ。
端的に言えば、どちらもただのメソッドであり、デリゲートである。

ちなみに最も冗長なラムダ式が下記である。

Predicate<int> judge =
 (int n) => { 
 if (n % 2 == 0) 
    return true; 
 else 
    return false; 
 }; 
var count = Count(numbers, judge);

デリゲート(Predicate<int>型)型の変数judgeを宣言し、その中にメソッドを代入している。
これを簡略化したものが、上述のシンプルなラムダ式である。

デリゲートを使う意味

デリゲートを使うと何が便利になるのだろうか。
それは、一つの関数で複数のことが可能になる、ということだ。たぶん。

 //デリゲートメソッドでtrueになったものの数を数えるメソッド
 //Predicate<int>は、int型を引数にしてbool型を返すデリゲート
    public int Count(int[] numbers, Predicate<int> judge)
    {
      int count = 0;
      foreach (var n in numbers)
      {
        if (judge(n) == true)
          count++;
      }
      return count;
    }

ここで定義したConuntメソッドを下記のように利用できる。

// numbersの中にある偶数の数をカウントする
var count = Count(numbers, n => n % 2 == 0);
// numbersの中にある奇数の数をカウントする
var count = Count(numbers, n => n % 2 == 1);
// numbersの中の5以上10未満の数をカウントする
var count = Count(numbers, n => 5 <= n && n < 10);
// numbers配列に数字の '1' が含まれている数をカウントする
var count = Count(numbers, n => n.ToString().Contains('1') );

このように同じCountメソッドだが、いろんな種類のカウントが可能になる。
Countメソッドの定義の中に、デリゲートを使った条件によるカウントの処理があるからだ。

デリゲートがない世界線

デリゲートが存在しないとしたらどうなるだろうか。
上述のようないろんなカウントを、逐一メソッドとして定義して利用する必要がある。
コード量が増える。中身のカウントの処理も何度もコード内に登場することにあるであろう。

なので、デリゲートをメソッドの中に内包させることによって、
一つのメソッドで複数の機能を利用できるようにした方が便利だという話。

共通部分は抽象化してまとめてしまおうという話。
なんか似たようなことやってね?という場合は、デリゲートを利用し共通化して、抽象度をあげた実装をされたし。

C# 以外のデリゲート例

ChatGPTさんに聞きました。

他の言語でもデリゲート型はありますか?あるだけ教えてください

答え。

はい、他の言語にもデリゲート型に似た機能を持つものがあります。以下はいくつかの例です。

Java:
Javaには、Java 8以降で導入された「ラムダ式」と呼ばれる機能があり、これを用いて関数型インターフェースを実装することで、デリゲート型に近い機能を実現することができます。

C++:
C++には、関数ポインタと呼ばれる機能があり、これを用いることで、関数をオブジェクトとして扱うことができます。

Python:
Pythonには、高階関数と呼ばれる機能があり、これを用いることで、関数を引数として別の関数に渡すことができます。

JavaScript:
JavaScriptには、コールバック関数と呼ばれる機能があり、これを用いることで、関数を引数として別の関数に渡すことができます。また、ES6以降では、アロー関数と呼ばれる機能が追加され、これを用いることで、デリゲート型に近い機能を実現することができます。

まとめ

デリゲートはメソッドを入れることができる箱(変数)
一つの箱に何個でもメソッド入れられるらしい。
delegate型は抽象クラスであり、実際には具象クラスのdelegate型がそれぞれある。
デリゲートが発火したときに、デリゲートの中のメソッドが発火する。
デリゲートには、匿名メソッドやラムダ式として直接メソッドを代入することも可能。

一言でいうと、メソッドを入れられる箱。以上。

Discussion