デリゲートについて

デリゲートについて

おはようございます。

今回は C#のデリゲートについて書いていきます。

デリゲートってなに?と思い調べていると「デリゲートとは英語で委任する、委譲するなどと訳され、他のオブジェクトへ処理を任せることができるもの」と解説されていたりして、余計分からなくなったので実際にコードを書いて動きを確かめながら分かりやすく解説して行きたいと思います!

デリゲートとはメソッドを変数に格納するデータ型

C#では変数にデータを格納する際に、int 型や string 型など型を指定しますが、メソッドを変数に格納する際にも型を指定する必要があります。

その際に指定するのが delegate 型になります。

delegate 型として通常のデータ型とは異なる、特殊なデータ型を宣言し変数にメソッドを格納して呼び出すことができます。

厳密にはメソッドへの参照を持つデータ型なので、メソッドを格納しているのではなく、メソッドの場所(メモリアドレス)を格納していることになります。

それではどのように記述し使用するのか見ていきましょう。

デリゲートでできること

  • delegate 型で変数に格納したメソッドを、変数名で間接的に呼び出すことができる

  • 複数のメソッドを格納することができる(マルチキャストデリゲート)

デリゲートの基本構文

// 修飾子 delegate 戻り値 デリゲート名
public delegate void TestDelegate1();

// 修飾子 delegate 戻り値 デリゲート名(引数, 引数,,,,)
public delegate string TestDelegate2(string message);

使い方

ここでは戻り値のないメソッド、戻り値・引数有りのメソッド、引数にデリゲート型を渡しコールバック関数を実行するメソッドの動作を確認していきます。

今回は Paiza オンライン実行環境を使用してテストしています。Visual Studio2022 で作成プロジェクトも GitHub にアップしていますので、興味のある方はご自身で色々いじってみてください。

戻り値のないメソッド

using System;

delegate void TestDelegate1();

class Hello{

    // クラス内で宣言してもOK
    // delegate void TestDelegate1();

    // コンソール実行時に一番最初に実行されるメソッド
    static void Main(){

        // new演算子を使用した記述方法
        // TestDelegate1 d1 = new TestDelegate1(TestFunc1);

        // new演算子を省略した記述方法
        TestDelegate1 d1 = TestFunc1;

        // 4.格納したメソッドを実行する(引数を定義していた場合は引数を代入する)
        d1(); // TestFunc1
    }

    // 2.デリゲート型の変数に格納するメソッドを作成する
    // アクセス修飾子は呼び出し元にそろえておく
    static void TestFunc1()
    {
        Console.WriteLine("TestFunc1");
    }
}

戻り値、引数有り

using System;

delegate string TestDelegate2(string message);

class Hello{

    static void Main(){

        TestDelegate2 d2 = TestFunc2;

        Console.WriteLine(d2("Hello TestFunc2")); // Hello TestFunc2
    }

    // 引数を渡せるメソッド
    static string TestFunc2(string msg)
    {
        return msg;
    }

}

引数にデリゲート型渡して実行する方法

using System;

// 引数にデリゲート型を持つ関数
public delegate string TestDelegate3(string message, CallBackDelegate cb);

// コールバック関数として渡す
public delegate string CallBackDelegate();

public class Hello{

		// 引数に文字列とデリゲートが渡ってくるメソッド
    public static string TestFunc3(string msg, CallBackDelegate cb)
    {
        return msg + cb();
    }

    // CallBackDelegate型として定義したデリゲートに指定するメソッド
    public static string CallbackFunc()
    {
        return " + Hello CallbackFunc";
    }

    public static void Main(){

        TestDelegate3 d3 = TestFunc3;
        CallBackDelegate cb = CallbackFunc;

        Console.WriteLine(d3("Hello TestFunc3", cb)); // Hello TestFunc3 + Hello CallbackFunc

    }
}

インスタンスメソッドを代入

インスタンスメソッドもデリゲートに格納することが出来ます。

using TestDelegate; // 追記

public class Hello
{

	// 中略

    public static void Main()
    {
      // 中略

      SubTestDelegateClass s = new SubTestDelegateClass();
      SubTestDelegate S = new SubTestDelegate(s.SubFunc);
      S(); // Hello SubTestDelegate Class
    }

    public delegate void SubTestDelegate();

}

namespace TestDelegate
{
    internal class SubTestDelegate
    {
        public void SubFunc()
        {
            Console.WriteLine("Hello SubTestDelegate Class");
        }
    }
}

マルチキャストデリゲート

デリゲートには+=演算子を使用して複数のメソッドを格納することが出来ます。

複数のメソッドを格納した状態でデリゲートを呼び出すと代入した全てのメソッドが呼び出されます。


namespace TestDelegate
{
    internal class InsertMultiMethod
    {

        public void func1()
        {
            Console.WriteLine("InsertMultiMethod func1");
        }

        public void func2()
        {
            Console.WriteLine("InsertMultiMethod func2");
        }
        public void func3()
        {
            Console.WriteLine("InsertMultiMethod func3");
        }
    }
}

public class Hello
{

	// 中略

    public static void Main()
    {
			  // 中略

				SubTestDelegateClass s = new SubTestDelegateClass();
        SubTestDelegate S = new SubTestDelegate(s.SubFunc);
        S(); // Hello SubTestDelegate Class

				// 追記箇所
				MultiMethodClass m = new MultiMethodClass();
        MultiMethodDelegate M = new MultiMethodDelegate(m.func1);
        M += m.func2;
        M += m.func3;
        M += m.func1; // 同じものを格納する事ができる

        M(); // 呼び出しは一箇所だが格納したメソッドが全て実行される
    }

    public delegate void SubTestDelegate();

		public delegate void MultiMethodDelegate(); // 追記

}

ここでポイントなのは、格納したメソッドは登録した順番に逐次実行されます。

イベントハンドラの様な使い方

リゲートはイベントハンドラを作成する際によく利用されます。

イベント(evant)とは、「クリックしたとき」「マウスが移動したとき」「値や状態が変化したとき」のようなコンピューター上で発生する何らかの事象のことを言います。そしてイベントハンドラは、イベントが発生した際に実行される処理のことです。

今回はサンプルとして、イベントハンドラのような動作をするものをデリゲートを使って作成しました。

実行すると、画像のような進捗率がコンソールに表示されます。

console

public class Hello
{
    public static void Main()
    {
        // プロパティにメソッドをセットする
        Progress += TestProgressHandler;

        Run();
    }

    // イベント処理用のデリゲート
    public delegate void ProgressHandler(int percent);

    // デリゲート型の変数をイベントとして使用する
    public static event ProgressHandler Progress;

    // イベントハンドラに追加するメソッド
    private static void TestProgressHandler(int percentage)
    {
        Console.WriteLine($"{percentage} %...");
    }

    public static void Run()
    {
        int max = 100;

        for (int i = 0; i <= max; ++i)
        {
            int percentage = (int)((double)i / (double)max * 100.0);

            // セットされたメソッドを実行する
            Progress(percentage);
        }
    }
}

まとめ

  • デリゲートとは関数を格納できる特殊なデータ型の変数
  • デリゲートには複数のメソッドを格納することが出来る(マルチキャストデリゲート)
  • 複数のメソッドを格納して呼び出した場合は、追加した順番に実行される
  • スタティックメソッドと、インスタンスメソッドの両方を格納することが出来る
  • イベントハンドラーの用に呼び出すことが出来る(厳密には EventHandler もデリゲート)

参考