C#のデリゲート(delegate)と関数ポインタの違い

概要
1. デリゲートについて
2.関数ポインタとデリゲートの違い等について
2-1.デリゲートにメソッドをいくつも登録可能
2-2.デリゲートをメソッドの引数に書くことが可能
2-3.デリゲートに登録されたメソッド情報を取得できる
2-4.別々に宣言されたデリゲートは独立している

1.デリゲートについて

デリゲートとは、C言語で言うところの関数ポインタのような役割です。

// 関数ポインタ型の宣言
typedef int(*Sample)(int x, int y);
 
// 適当な関数
int func(int x, int y)
{
    return (x * y);
}
 
// ポインタへ関数を格納
Sample sample = func;
// 関数の呼び出し
int result = sample(1,2);

C言語では関数ポインタを用いると間接的に関数を呼び出すことができました。
C#でもそれに対応する機能があり、デリゲートを呼ばれています。
↑のC言語例をC#に変換すると次のようになります。

// デリゲート宣言
delegate int Sample(int x, int y);

class Class1
{
    // 適当な関数(メソッド)
    public int func(int x, int y)
    {
        return (x * y);
    }
}

static void Main()
{
    // デリゲート生成
    Sample sample = new Sample();
    // 関数をデリゲートに格納
    sample = (new Class1).func;
    // 関数の呼び出し
    int result = sample(1,2);
}

このような感じでデリゲート経由で関数を呼び出すことができました。
C言語の関数はC#ではメソッドと呼ばれますので、以後、関数の事をメソッドと呼びます。


2.関数ポインタとデリゲートの違い等について

デリゲートとは関数ポインタであると言いましたが、違う点がいくつかあります。

2-1.デリゲートにメソッドをいくつも登録可能

デリゲートにはメソッド複数格納可能できます。
関数ポインタは1つだけでしたので、正確に言えば、デリゲートとは関数ポインタの配列であると言えます

// デリゲート宣言
delegate int Sample(int x, int y);

static void Main()
{
    // デリゲート生成
    Sample sample = new Sample();
    // 最初のメソッドをデリゲートに格納
    sample = func1;
    // 次のメソッドをデリゲートに追加
    sample += func2;
    // 次のメソッドをデリゲートに追加
    sample += func3;

    // 関数の呼び出し
    int result = sample(1,2);
}

このような形で、デリゲートには複数メソッドが登録出来ました。
このデリゲートを実行させると、

 ・func1
 ・func2
 ・func3

の順番でメソッドが呼び出されます。
ただし、戻り値に関しては、最後に呼び出されたfunc3が有効となります。


2-2.デリゲートをメソッドの引数に書くことが可能

デリゲートはメソッドの引数内に記述できます。
これを利用すれば、そのメソッド内でデリゲートを実行させる事が可能です。
ただしこれに関しては、関数ポインタでも実現できます。

// デリゲート宣言
delegate int Sample(int x, int y);

// メソッドの引数にデリゲートを含める
static int RunDelegate(int x, int y, Sample func)
{
    // デリゲートの呼び出し
    return func(x, y);
}

static void Main()
{
    // デリゲート生成
    Sample sample = new Sample();
    // メソッドをデリゲートに格納
    sample = func1;

    // 関数の呼び出し
    int result = sample(1,2);
}


2-3.デリゲートに登録されたメソッド情報を取得できる

デリゲートは関数ポインタ配列を持ったクラスのようなものです
クラスなので、その他にも情報を保存しておく事ができます。
例えば登録されたメソッド名等です。
これはC言語の関数ポインタにはできません。

static void Main()
{
    // デリゲート生成
    Sample sample = new Sample();
    // メソッドをデリゲートに格納
    sample = func1;
    // メソッド名を取得
    string MethodName = sample.Method.Name;
}

↑はデリゲート宣言等は省略しています。
Mehod.Nameというプロパティによってメソッド名を取得しています。
ただし、取得出来るのは最後に追加されたメソッドに限られるようです。
いくつか追加すれば、最後のメソッド名のみが取得出来るという事です。


2-4.別々に宣言されたデリゲートは独立している

デリゲートを別々に宣言すると、同じメソッドの型を持っていたとしても別々の型として扱われます

// 同じメソッド型を持つデリゲートを2つ宣言
delegate int Sample1(int x, int y);
delegate int Sample2(int x, int y);

static void Main
{
    // デリゲートを生成
    Sample1 sample1 = new Sample1();
    Sample1 sample1-1 = new Sample1();
    Sample2 sample2 = new Sample2();

    // 適当なメソッドを格納
    sample1 = func1;
    sampel2 = func2;

    // デリゲート同士を結合させる(コンパイルエラー)
    sample1 += sample2;
    // デリゲート同士をキャストを用いて結合させる(コンパイルエラー)
    sample1 += (Sample1)sample2;
    // デリゲート同士を結合させる(これはOK)
    sample1 += sample1-1;
}

↑のデリゲート結合は2つともコンパイルエラーとなります。
同じメソッド型同士であっても、別々に宣言した時点で、別の型として扱われる為です。
sample1とsample1-1は同じ型のデリゲートから生成してますので、この間で結合させることは可能となっています。

以上になります。


参考:
連載:C#入門 第17回 処理を委譲するdelegate