C#

【C#】デリゲートの基本

この記事ではC#のデリゲート(delegate)について解説します。デリゲートはプログラムを勉強していると、つまづきやすい項目の一つではないでしょうか?ここでは、初心者にも理解できるようにわかりやすくシンプルに説明していきます。

デリゲートとは

デリゲートとは代表や委譲という意味です。C#ではメソッドを変数に格納する仕組みです。…って言われても何やらよくわかりません。そしていきなりよくわからないコードを突き付けられる…それがデリゲートです。

「変数にメソッドを格納する」とは?

ここでも、よくわからないコードを突き付けます。でもゆっくり説明していきますので、大丈夫です。

コード

using System;

internal class Program
{
    delegate int SampleDel(int x, int y);

    static void Main(string[] args)
    {
        SampleDel delVar;
        delVar = new SampleDel(MethodPlus);
        int ans = delVar(1, 2);
        Console.WriteLine("ans:{0}", ans);
    }

    static int MethodPlus(int a, int b)
    {
        Console.WriteLine("MethodPlusが呼び出されました");
        return a + b;
    }
}

出力結果

MethodPlusが呼び出されました
ans:3

まず上記のコードには、クラスが2つあり、一つはMainメソッドがあるProgramクラスです。Programクラスには、MainメソッドのほかにMethodPlusという2つの整数の和を求める静的メソッドがあります。

次に5行目を見てみましょう。このdelegateで始まる文がデリゲートの宣言になります。この宣言はdelegateキーワードを用いて下記のように記述します。

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

ここで「デリゲート型名」というものが出てきます。その名の通り「デリゲート型」の名前のことなのですが、デリゲートは前述したように「メソッドを変数に格納する仕組み」です。変数なのにメソッドを格納できる、というイメージはわかりにくいですが、このメソッドを格納する変数の型が「デリゲート型」になります。さらにわかりずらいところなのですが、この「デリゲート型」自体は変数ではなく、変数の型名です。型ですからクラスのようなイメージですね。この宣言には戻り値の型と仮引数リストが含まれていますが、この「デリゲート型名」で宣言されるデリゲート変数の戻り値の型と仮引数リストを表しています。この「「デリゲート名(型)」で宣言されるデリゲート変数」というのは、具体的にはMainメソッド内(9行目)で宣言されているdelVarです。この5行目では、戻り値の型がint型でint型のaとint型のbという2つの引数を持つデリゲート名SampleDelが宣言されていて、9行目でdelVarというメソッドを格納するデリゲート変数が宣言されている、ということになります。

次にMainメソッドを見ていきます。前述したように、9行目では5行目で宣言したSampleDelというデリゲート型の変数delVarを宣言しています。10行目では9行目で宣言した変数のインスタンス化を行っています。ここで変数delVarにMathodPlusというメソッドを登録しています。SampleDel型の変数delVarに代入するメソッドは、5行目で宣言したSampleDelと同じ引数リストおよび戻り値の型を持つメソッドでなければなりません。

ややくどい説明ですが、一見、2回も宣言を繰り返しているように見えるのが、理解しにくいところです。改めて書くと下記のようになります

  • デリゲート型の宣言(5行目)
    ➡ここでメソッドの型(仮引数リスト、戻り値の型)を決める
  • デリゲート変数の宣言(9行目)
    ➡ここでメソッドを格納する変数名が決まる
  • デリゲート変数のインスタンス化(10行目)
    ➡ここで格納されるメソッドが決まる

なお、この9~10行目の変数の宣言とインスタンス化ですが、もちろん下記のように1行で書くことも可能です。

SampleDel delVar = new SampleDel(MethodA);

また、new SampleDel()を省略して、

SampleDel delVar = MethodA;

のように書くこともできます。

またデリゲート型の宣言はクラスの外に出すこともできます。この場合、宣言されたデリゲート型は複数のクラスで使用できます。

インスタンスメソッドも格納できる

ここまでの例ではデリゲート変数に静的メソッドを格納しましたが、次の例のようにインスタンスメソッドを格納することもできます。

コード

using System;

internal class Program
{
    delegate int SampleDel(int x);

    static void Main(string[] args)
    {
        Test t = new Test(5);
        SampleDel delVar=new SampleDel(t.MethodPlus);

        int ans = delVar(3);
        Console.WriteLine("ans:{0}",ans);
    }
}

internal class Test
{
    int val; //インスタンスメンバ

    //コンストラクタ
    public Test(int v)
    {
        this.val = v;
    }

    //インスタンスメソッド
    public int MethodPlus(int a)
    {
        Console.WriteLine("MethodPlusが呼び出されました");
        return a + val;
    }
}

出力結果

MethodPlusが呼び出されました
ans:8

上記コードでは5行目でデリゲートの宣言、10行目でデリゲート変数の宣言とインスタンス化をしています。この10行目のインスタンス化のところでデリゲート変数にインスタンスメソッドを格納しています。インスタンス変数も使用されている点に注意です。

デリゲート変数

メソッドを引数として渡す

メソッドをデリゲート変数に格納できるので、メソッドを引数として渡すこともできます。以下に例を示します。

コード

using System;

internal class Program
{
    delegate int SampleDel(int x, int y);

    static void Main(string[] args)
    {
        SampleDel delVar = new SampleDel(MethodPlus);
        MethodDel(delVar,2,3);
    }

    static int MethodPlus(int a, int b)
    {
        Console.WriteLine("MethodPlusが呼び出されました");
        return a + b;
    }

    static void MethodDel(SampleDel d, int a, int b)
    {
        int ans = d(a, b);
        Console.WriteLine("ans:{0}", ans);
    }
}

出力結果

MethodPlusが呼び出されました
ans:5

上記のコードでは、9行目で宣言したデリゲート変数delVarを10行目のMethodDelに渡しています。MethodDelのひとつめの引数はSampleDel型デリゲート変数になります。SampleDel型のデリゲート変数はMethodDel内で仮引数dとして渡されており、MethodDel内でメソッド名としてdが使用されています。このようにデリゲートを利用することでメソッドを引数として渡すことができます。

メソッドを戻り値にする

メソッドを引数としたのと同じように、メソッドを戻り値にすることもできます。下記に例を示します。

コード

using System;

internal class Program
{
    delegate int SampleDel(int x, int y);

    static void Main(string[] args)
    {
        SampleDel delVar = defineDel();

        int ans = delVar(2, 3);
        Console.WriteLine("ans:{0}", ans);
    }

    static int MethodPlus(int a, int b)
    {
        Console.WriteLine("MethodPlusが呼び出されました");
        return a + b;
    }

    static SampleDel defineDel()
    {
        SampleDel d = new SampleDel(MethodPlus);
        return d;
    }
}

出力結果

MethodPlusが呼び出されました
ans:5

上記コードでは、9行目のデリゲート変数delVarの宣言のときに、SampleDel型の戻り値を返すメソッドdefineDelを呼び出しています。このdefineDelメソッドのように戻り値としてデリゲート変数(すなわちメソッド)を返すことにも利用できます。

マルチキャスティング

デリゲート変数には、戻り値の型と引数リストが同じなら、複数のメソッドを登録することが可能です。この機能はマルチキャスティングと呼ばれています。具体的に見ていきましょう。

コード

using System;

internal class Program
{
    delegate int TestDel(int x, int y);

    static void Main(string[] args)
    {
        int ans;
        Test t1 = new Test(10); //val=10でインスタンス化
        Test t2 = new Test(20); //val=20でインスタンス化
        Test t3 = new Test(30); //val=30でインスタンス化

        TestDel delVar = null;
        delVar = delVar +  t1.MethodMulti + t2.MethodMulti + t3.MethodMulti;
        delVar += MethodPlus;
        delVar += t2.MethodMulti;
        delVar += t3.MethodMulti;

        ans = delVar(2, 3);
        Console.WriteLine("ans:{0}", ans);

        delVar -= t2.MethodMulti;
        delVar = delVar - t3.MethodMulti;
        ans = delVar(2, 3);
        Console.WriteLine("ans:{0}", ans);
    }

    static int MethodPlus(int a, int b)
    {
        Console.WriteLine("a={0},b={1}でMethodPlusが呼び出されました", a, b);
        return a + b;
    }
}

internal class Test
{
    int val;

    //valを定義するコンストラクタ
    public Test(int v)
    {
        val = v;
    }

    //val倍するインスタンスメソッド
    public int MethodMulti(int p, int q)
    {
        Console.WriteLine("val={0},p={1},q={2}でMethodMultiが呼び出されました", val, p, q);
        return val * (p + q);
    }
}

出力結果

val=10,p=2,q=3でMethodMultiが呼び出されました
val=20,p=2,q=3でMethodMultiが呼び出されました
val=30,p=2,q=3でMethodMultiが呼び出されました
a=2,b=3でMethodPlusが呼び出されました
val=20,p=2,q=3でMethodMultiが呼び出されました
val=30,p=2,q=3でMethodMultiが呼び出されました
ans:150
val=10,p=2,q=3でMethodMultiが呼び出されました
val=20,p=2,q=3でMethodMultiが呼び出されました
val=30,p=2,q=3でMethodMultiが呼び出されました
a=2,b=3でMethodPlusが呼び出されました
ans:5

上記コードでは、14行目でデリゲート変数delVarが宣言されnullで初期化されています。nullで初期化しない場合、15行目でコンパイルエラー(未割り当てのローカル変数が使用されました)が出ます。

次の15行目では+演算子により、 t1.MethodMultiとt2.MethodMultiとt3.MethodMultiが追加で登録されています。また16~18行目では+=演算子を用いてメソッドが登録されています。このように+演算子や+=演算子を用いることで複数のメソッドを登録できます。14~18行目までで合わせて6つのメソッドが登録されています。なお、この6つのメソッドのうちt2.MethodMulti とT3.MethodMultiは2回重複して登録されています。その後、20行目で戻り値を求めて、21行目で戻り値の結果を表示しています。
ここまでの結果を見てみると、20行目で呼び出されたdelVarは6つのメソッドを登録された順に実行しています。しかし、戻り値は最後に実行されたt3.MethodMultiの結果となります。このように、デリゲートのマルチキャスティングでは、登録された順に実行され、戻り値は最後に登録されたメソッドの戻り値のみになります。

次に23,24行目では-演算子および-=演算子を使ってt2.MethodMultiメソッドとt3.MethodMultiメソッド を削除しています。このように、-演算子や-=演算子を使うことで一度登録されたメソッドを削除することができます。なおt2.MethodMultiメソッドは直服して2回登録されていましたが、ここで削除後の25行目の実行結果を見ると、2回目に登録されたほうが削除されていることがわかります。このように-=演算子では、登録されたメソッドに重複があった場合には、後に登録されたほうが削除されます。

終わりに

ここではC#のデリゲートの基本的な動作について説明しました。デリゲートは最初はとっつきにくいですが慣れる、慣れるとそんなに難しくないですよね。

デリゲートの関連項目として【C#】匿名メソッドとラムダ式の基本 もご覧ください。

ABOUT ME
しかすけ
日々是好日。何とか何とか生きてます。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です