C#のデリゲートを理解する

C#のデリゲートを理解する

デリゲートとはなんだ?

C#にはデリゲートという機能が存在する。今回はそれについて理解を深めてみようと思う。

まず、デリゲートと聞いてパッと言葉のイメージがつかないのは自分だけではないはず。もっとイメージが湧くワードをチョイスしてもらいたかったものである。

デリゲートを一言でざっと説明するなら、メソッドを代入できる型のことである。

int型に代入するなら int a = 1;と書くし、string型に文字をいれるなら string moji = “aaa”; のように書くと思う。ならメソッドを代入するにはどうしたらいいだろうか??

このようなときにメソッドを代入できるのがデリゲートである。デリゲートにメソッドを代入してみると以下のようになる。ここで「sumDelegate」と「sumDele」と「Sum」の名称は任意で設定したものなので、自分で自由に設定することができる。

sumDelegate sumDele = Sum;

(デリゲートの型名)(デリゲートの変数名)= (代入するメソッド名);

ここで「sumDelegate」は先にデリゲートの型として定義されているものとし、「Sum」というメソッドも他で定義されているものとする。このとき、「sumDelegate」の型の定義と「Sum」の型の定義は一致している必要があるので注意。例えば、int型の変数に文字列を代入できないのと同じで、デリゲートの型式と一致していないメソッドは代入できないのである。

では実際にメソッドをデリゲートに代入して実行するプログラムを動かしてみる。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication
{
    /// デリゲートの型を定義します。
    /// ここでは戻り値がvoidでint型の引数が2個ある型を定義してみる。
    public delegate void sumDelegate(int a, int b);
    
    class Program
    {
        static void Main(string[] args)
        {
            // メソッドをデリゲートに代入
            sumDelegate sumDele = Sum;
            
            // 引数を入れて実行するとSumメソッドの処理が呼ばれる
            sumDele(1, 2);
        }

        /// 引数を足し合わせて、コンソールに表示します。
        static void Sum(int a, int b)
        {
            int sum = a + b;
            Console.WriteLine(sum.ToString());
            Console.Read();
        }     
    }
}

上記を実行するとコンソールに「3」が表示されます。

簡単に内容を解説していく。まずはデリゲートの型を定義している。これはメソッドを代入するために必要となるからである。

次に、デリゲートの型と一致するメソッドを用意する。これは戻り値と引数があっていればOK。

最後に、デリゲートの型でデリゲート変数を用意し、これにメソッドを代入している。

この段階まで実施するとデリゲート変数にはメソッドが代入されていることになる。つまり、デリゲート変数で代入されたメソッドを実行できるようになるのである。

ここでは「sumDele」というデリゲート変数に「Sum」というメソッドを代入しているので、「sumDele」は「Sum」のメソッドを実行することができる。

よって「sumDele」に引数を入れて実行すると「Sum」のほうの処理が実行され、結果がコンソールに表示されるということになる。

デリゲートを2個用意してみる

試しにデリゲートを2個用意してみます。上記の「デリゲート型名」と「デリゲート変数名」と「メソッド名」の部分を他の名前にして追加してみます。2個目のデリゲートを追加してみたプログラムがこちらです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication
{
    /// デリゲートの型を定義します。
    /// ここでは戻り値がvoidでint型の引数が2個ある型を定義してみる。
    public delegate void sumDelegate(int a, int b);
    public delegate void SingleDelegate(int a);

    class Program
    {
        static void Main(string[] args)
        {
            // メソッドをデリゲートに代入
            sumDelegate sumDele = Sum;
            SingleDelegate singleDele = Single;

            // 引数を入れて実行するとSumメソッドの処理が呼ばれる
            sumDele(1, 2);
            singleDele(5);
        }

        /// 引数を足し合わせて、コンソールに表示します。
        static void Sum(int a, int b)
        {
            int sum = a + b;
            Console.WriteLine(sum.ToString());
            //Console.Read();
        }

        /// 引数をコンソールに表示します。
        static void Single(int a)
        {
            int sum = a;
            Console.WriteLine(sum.ToString());
            Console.Read();
        }

    }
}

結果はコンソールに「3」と「5」が表示されます。このことからデリゲートでは任意の名前を設定できることが分かります。よって、デリゲートでは同じメソッドを実行したいときでも別々のデリゲート変数名として実行することができるので、メソッドの呼ぶタイミングなどを柔軟に操作できるようになります

以上がC#のデリゲートのもっとも基本的な考え方です。

複数のメソッドをデリゲートに入れてみる(マルチキャストデリゲート)

上記まででは1つのデリゲート変数に1つのメソッドを代入して実行していた。では、1つのデリゲート変数に2つのメソッドを入れることはできるだろうか??

答えはそう。できる。

例えば、int型の配列に複数の数値を格納することができるように、デリゲート変数も配列のように複数のメソッドを入れることができる。このとき、デリゲート変数とメソッドの型は一致していなければならない。

実際にデリゲートに複数のメソッドを入れて実行してみよう。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleDelegate_Multicast
{
    /// デリゲートの型を定義します。
    public delegate void MultiDelegate(int a, int b);

    class Program
    {
        static void Main(string[] args)
        {
            // 1個目のメソッドをデリゲートに代入
            MultiDelegate multiDele = Sum;

            // 2個目のメソッドをデリゲートに追加
            multiDele += Sub;

            // 引数を入れて実行するとSumとSubのメソッドの処理が同時に呼ばれる
            multiDele(3, 2);
        }

        /// 足し算して、コンソールに表示します。
        static void Sum(int a, int b)
        {
            int sum = a + b;
            Console.WriteLine(sum.ToString());
            //Console.Read();
        }

        /// 引き算して、コンソールに表示します。
        static void Sub(int a, int b)
        {
            int sub = a - b;
            Console.WriteLine(sub.ToString());
            Console.Read();
        }

    }
}

上記を実行すると足し算と引き算が実行されてコンソールに「5」と「1」が表示される。つまりは1つのデリゲート変数を実行するだけで2つのメソッドを実行することができたのである。このようにデリゲートには複数のメソッドを追加して実行する機能がある。

デリゲート変数に2つ目のメソッドを追加したい場合は以下のように記載すればよい。

multiDele += Sub;

(デリゲートの変数名)+= (追加するメソッド名);

このように記載すればデリゲート変数にどんどん他のメソッドを追加していくことができる。この複数のメソッドが入れられた状態のデリゲート変数のことをマルチキャストデリゲートと呼ぶ。

異なるクラスのメソッドを実行したときにデリゲートを実行する

次に異なるクラスのメソッドを実行したときにデリゲートを実行してみよう。今までは同じクラス内でデリゲート変数を用意してそれにメソッドを代入して実行していた。

そこで今回は異なるクラス間でデリゲートを実行してみる。

まずは呼び出し元となるmain()が含まれるクラスを用意する。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace null_Delegate
{
    class Program
    {
        static void Main(string[] args)
        {
            Class1 c1 = new Class1();
            c1.f1 = a;
            c1.Test();
        }

        static void a()
        {
            Console.WriteLine("I'm a().");
        }
    }
}

このクラスではまず、Class1の内容が扱えるようにClass1のインスタンスを作成している。Class1にはデリゲート型とデリゲート変数が用意してあるので、それのデリゲート変数にメソッドaを代入する。

これでメソッドf1を実行するとメソッドaが実行されるようになっている。

次にClass1はこのようになっている。

using System;

namespace DelegateTest1
{
	class Class1
	{
		public delegate void f1Delegate();
		public f1Delegate f1;

		public void Test()
		{
			if (f1 != null)
			{
				f1();
			}
		}
	}
}

Class1ではまずデリゲート型とデリゲート変数が用意されている。これでデリゲート変数にメソッドを代入する準備はできている。Class1をインスタンス化してしまえば他のクラス内でデリゲートにメソッドを代入することもできる。

さらにTestメソッドを用意し、Testメソッドが実行されたら内部でf1デリゲートが実行されるようになっている。先に述べたように、f1デリゲートにはすでにaメソッドが代入されているので、これを実行するとaメソッドの中身が実行されるようになっている。

つまりmainメソッド内からインスタンス化されたClass1のテストメソッドを実行すると、デリゲートを経由してaメソッドが実行されるようになっている。

このようにすることで、あるメソッド呼び出したあとだけに実行したいメソッドなどを定義することができる

ここまで理解できた。でも次のような疑問はまだ拭いきれない。

でもさ、でもさ、そういうときはデリゲートなんて経由しないで直接メソッドを呼んじゃえばよくね??

という疑問に対して次の項目で考えてみよう。

直接メソッドを呼ばすにデリゲートを経由するメリット

直接メソッドを呼ばすにデリゲートを経由するメリットを考えてみる。きっと直接呼ばないようが便利な場合もあるのだろう。

そこで、上記で実行したプログラムにランダムな分岐処理を入れて試してみる。ProgramとClass1クラスを書き換えて実行してみます。まずはProgramクラス↓

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace null_Delegate
{
    class Program
    {
        static void Main(string[] args)
        {
            Class1 c1 = new Class1();
            c1.f1 = a;
            c1.Test();
        }

        static void a()
        {
            Console.WriteLine("I'm a().");
            Console.ReadLine();
        }
    }
}

次にClass1クラス↓

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace null_Delegate
{
    class Class1
    {
        public delegate void f1Delegate();
        public f1Delegate f1;

        public void Test()
        {
            Random rnd = new Random();
            int i = rnd.Next(10);

            if (i < 6) {

                Console.Write("デリゲートf1が実行されます。\n");
                if (f1 != null)
                {
                    f1();
                }
            }
            else {

                Console.Write("デリゲートf1は実行されませんでした。\n");
                Console.ReadLine();

            }
        }
    }
}

上記を実行すると、デリゲートが呼ばれるときと呼ばれないときが出てきます。

先にランダムクラスで10以下の整数を作成し、6に満たない場合にだけデリゲートを実行するようにしています。このようにすれば、呼び出し元の処理は変更しなくても、呼び出し先の状態によってメソッドを呼ぶときの条件を設定することができます

これがメソッドを直接呼ばずにデリゲートを経由するときのメリットになると思われます。

これができなければ、呼び出し元から呼び出し先のメソッドを実行するタイミングで先に動作を決めて渡してあげる必要が出てきます。そうではなく、呼び出し先の結果によって実行する処理を変えたいときにはこのようなデリゲートを経由して実行する方法を使ったほうがメリットがあるでしょう。

 

以上がデリゲートに複数のメソッドを入れてみたときの基本的な考え方である。

雑記カテゴリの最新記事