2016年2月27日土曜日

デリゲートとはなにか(C#)

C#はjavaとよく文法が似ていて、初見ではすごくjavaっぽい印象を受けます。でも実際はjava以外からも色々なプログラミング言語を参考にしています。ゆえにjavaとは違う部分も実は色々とあります。

それらの違いのなかで、もともとjavaをやっていた僕が特にひっかかったのがデリゲート(delegate)というものでした。delegateは委譲という意味の英単語でなにかを「譲る」ためのものだということはわかるのですが、「なにを」「どういう目的で」譲るのかさっぱりわかりません。

そこで今回はこのデリゲートについての話をしてみたいと思います。デリゲートとは、簡単にいえば「メソッドを代入できるもの」です。「値を代入できるもの」といえば変数ですが、デリゲートにはメソッドを代入することができます。


コードを書いてみる


といっても実際にコードを見てみないことにはよくわかりませんね。そこでコードを実際に見てみます。
namespace ConsoleTest
{
    delegate void DelegateDayooo(int a); //デリゲート型を定義しました。
    class Program
    {
        static void Main(string[] args)
        {
            //DelegateDayooo型の変数aを定義
            DelegateDayooo a = new DelegateDayooo(methodDayo);
            a(1);   //変数aを介してmethodDayoというメソッドを使っています。
            a(2);
            a(3);
        }
        static void methodDayo(int n)
        {
            Console.WriteLine(n + " という数字が入力されました。");
        }
    }
}
上記のプログラムはDelegateDayoooというデリゲート型を定義し、DelegateDayooo型の変数aを定義、aを介してProgramクラスのmethodDayoメソッドを呼び出しています。

実行結果
こういうふうにデリゲート型を定義することでデリゲート型の変数を通してクラスのメソッドを呼び出すことができます。delegate void DelegateDayooo(int a);という風に定義しましたが、これは「何も値を返さないvoid型で、int型の引数をひとつ持つメソッドを代入できるDelegateDayoooという名前のデリゲート型」という意味です。

もしint型の値を返し、int型の引数をひとつ持つメソッドを代入できるDelegateDayoooIntという名前のデリゲート型を定義したいのであれば次のようにdelegate int DelegateDayoooInt(int b);と書きます。
namespace ConsoleTest
{
    delegate void DelegateDayooo(int a); //デリゲートを定義しました。
    delegate int DelegateDayoooInt(int b); //2つめ
    class Program
    {
        static void Main(string[] args)
        {
            DelegateDayooo a = new DelegateDayooo(methodDayo);
            a(1);
            a(2);
            a(3);
            DelegateDayoooInt b = new DelegateDayoooInt(methodDayo2);
            Console.WriteLine(b(1));
            Console.WriteLine(b(2));
            Console.WriteLine(b(3));
        }
        static void methodDayo(int n)
        {
            Console.WriteLine(n + " という数字が入力されました。");
        }
        static int methodDayo2(int n1)
        {
            n1 = n1 * 10;
            return n1;
        }
    }
}
実行結果

ちなみにC# 2.0 から DelegateDayoo a = methodDayo;という書き方ができます。こういう書き方のほうが普通の変数の代入(int a = b;とか)と同じで、直感的に使えていいかもしれません。
namespace ConsoleTest
{
    delegate void DelegateDayooo(int a); //デリゲートを定義しました。
    delegate int DelegateDayoooInt(int b); //2つめ
    class Program
    {
        static void Main(string[] args)
        {
            DelegateDayooo a = methodDayo; //こういう書き方もできます
            a(1);   //変数aを介してmethodDayoというメソッドを使っています。
            a(2);
            a(3);
            DelegateDayoooInt b = methodDayo2;
            Console.WriteLine(b(1));
            Console.WriteLine(b(2));
            Console.WriteLine(b(3));
        }
        static void methodDayo(int n)
        {
            Console.WriteLine(n + " という数字が入力されました。");
        }
        static int methodDayo2(int n1)
        {
            n1 = n1 * 10;
            return n1;
        }
    }
}

ちなみにデリゲートにはstaticメソッド、インスタンスメソッドどちらでも代入することができます。さっきはstaticメソッドを代入したので今度はインスタンスメソッドを代入します。
namespace ConsoleTest
{
    delegate void DelegateDayooo(int a); //デリゲートを定義しました。
    delegate int DelegateDayoooInt(int b); //2つめ
    class Program
    {
        static void Main(string[] args)
        {
            //spというインスタンスを作ります。
            SubProgram sp = new SubProgram();
            DelegateDayooo a = sp.methodDayo;
            a(1);   //変数aを介してmethodDayoというメソッドを使っています。
            a(2);
            a(3);
            DelegateDayoooInt b = sp.methodDayo2;
            Console.WriteLine(b(1));
            Console.WriteLine(b(2));
            Console.WriteLine(b(3));
        }
    }
    class SubProgram {
        public void methodDayo(int n)
        {
            Console.WriteLine(n + " という数字が入力されました。");
        }
        public int methodDayo2(int n1)
        {
            n1 = n1 * 10;
            return n1;
        }
    }
}
実行結果。staticメソッドを代入したときと同じ。

ちなみにDelegate型への代入時にmethodDayo();というふうにメソッドのかっこ()まで書いてしまうとエラーになります。
DelegateDayooo a = sp.methodDayo();  //←この赤いかっこはいらない
 DelegateDayooo a = new DelegateDayooo(methodDayo()); //これもエラーになる 
「メソッド名が必要です。」というエラーはかっこまで書いちゃってることが原因であることが多いです。初心者は注意。

+= 演算子を用いることでデリゲートに複数のメソッドを詰め込むことができます。複数のメソッドをひとつのデリゲートにいれることをマルチキャストデリゲートといいます。
namespace ConsoleTest
{
    delegate void DelegateDayooo(); //デリゲートを定義しました。
    class Program
    {
        static void Main(string[] args)
        {
            SubProgram sp = new SubProgram();
            DelegateDayooo a = sp.methodDayo;
            a += sp.methodDayo2;
            a += sp.methoddayo3; //変数aにメソッドが3つも入っています
            a();  //これを実行。
        }
    }
    class SubProgram {
        public void methodDayo()
        {
            Console.WriteLine("おはよう");
        }
        public void methodDayo2()
        {
            Console.WriteLine("こんにちは");
        }
        public void methoddayo3()
        {
            Console.WriteLine("こんばんは");
        }
    }

実行結果。変数aを一回実行するだけで全部のメソッドが実行される。

ちなみにマルチキャストではstaticメソッドもインスタンスメソッドも混ぜて複数代入することできます。


さらにこんなふうに非同期処理だってできちゃいます。

using System;
using System.Threading;
namespace ConsoleTest
{
    delegate void DelegateDayooo(int n); //デリゲートを定義しました。
    class Program
    {
        static void Main(string[] args)
        {
            //spというインスタンスを作ります。
            SubProgram sp = new SubProgram();
            DelegateDayooo a = sp.methodDayo;
            DelegateDayooo b = sp.methodDayo2;
            // aを非同期で呼び出す。
            IAsyncResult aHidouki = a.BeginInvoke(1, null, null);
            // bを非同期で呼び出す。
            IAsyncResult bHidouki = b.BeginInvoke(2, null, null);
            //非同期処理が終わるのを待つ。
            a.EndInvoke(aHidouki);
            b.EndInvoke(bHidouki);
        }
    }
    class SubProgram {
        public void methodDayo(int n1)
        {
            for(int i = 0; i <=10; i++)
            {
                Console.WriteLine(i + "メソッド" +n1);
            }
        }
        public void methodDayo2(int n2)
        {
            for (int i = 0; i <= 10; i++)
            {
                Console.WriteLine(i + "メソッド" + n2);
            }
        }
    }
}

実行結果。