LoginSignup
4
1

More than 1 year has passed since last update.

C#のメソッドのdelegate(デリゲート)とラムダ式(=>) - 実はC言語の関数へのポインタに似てる!

Last updated at Posted at 2020-10-02

そもそもC#でdelegate(デリゲート)って何??どういうもん??

C#の記事

最近C#のソースコードを見て、=>やdelegateというキーワードを目にしましたが、これはよく調べてみたら、C言語でいう、関数へのポインタ、すなわち関数を参照する変数じゃないか!!( ˶˙ᵕ˙˶ )って目が覚めたんです

英和辞典で調べてみても、delegateって「代表として派遣する」とか「委任する」とかあるけど、C#の世界だと「関数という一連の作業を、委任する」という意味になるのかな??
う~ん…なっかなか難しい発想…(*´꒳`*)

話は戻せば、すなわちdelegateは関数を参照する変数となれば、

  • 変数はオブジェクトを参照する

それと同じように

  • 変数は関数もオブジェクトと同じように参照する

その方法が、delegate(デリゲート)の働きだって理解できたんです( ˶˙ᵕ˙˶ )

どうやって使うんだ??

まず、デリゲートは関数を参照する型を定義することから行う必要があります。どの引数を使って、どの戻り値の型を定義しないと話にならないでしょう。。。

宣言の方法は…

// クラスの定義
public class SomeClass
{
    private int value1
    private int value2
    
    public void method1(int a, int b)
    {
        this.value1 = a;
        this.value2 = b;
    }
}

// デリゲート(関数を参照する変数の型…引数や戻り値の型の定義)
public delegate void MyDelegate(string s);

そんで、実際にインスタンスを定義するときも、newを使って…

// クラスのインスタンス作成の仕方
SomeClass class1 = new SomeClass();

// デリゲートのインスタンス(関数そのものを参照するインスタンス)の作成は…
MyDelegate dgref1 = new MyDelegate(Console.WriteLine);

というようにやるのですが「MyDelegate dgref1 = Console.WriteLine」のように関数をそのまま代入演算子で代入するもんだと思っていました( ˙꒳​˙ᐢ )でもC#の場合はそうでないようです。。。

new演算子で、()の中に、戻り値の型と引数が一致するメソッド名を代入することで、関数を参照するという、最初はそこで頭を悩ませました꒰ᐢ⸝⸝• ·̭ •⸝⸝ᐢ꒱

でも匿名メソッドというのは最近のC#ではできるので、その場合はちょっとやり方が違ってくるので後で説明します

C#で実際メソッド自体の参照をを試してみた

まずはメソッドをdelegateで参照してみる

まずは簡単な例として、1つのメソッドを参照して、それを呼び出してみることにしました(˶ ・ᴗ・ )੭

Program.cs
using System;

namespace MyTrainingCsharp1
{
    // 2値を計算するメソッドデリゲート型の定義
    delegate double Culc2(int a, int b);
    
    class Program
    {
        // メイン関数(プログラム開始)
        static void Main(string[] args)
        {
            Console.WriteLine("Hello Ramda!");

            // 2値計算メソッドデリゲート変数「culc2Test」を定義する。ここではplus(a,b)を使う
            Culc2 culc2Test = new Culc2(plus);

            // デリゲート変数に格納されたメソッドを呼び出す
            double testnum = culc2Test(24, 33);

            Console.WriteLine("答えは" + testnum + "です。");
        }

        // 足し算
        static double plus(int a, int b)
        {
            return (double)a + (double)b;
        }

        // 引き算
        static double minus(int a, int b)
        {
            return (double)a - (double)b;
        }

        // 掛け算
        static double mltpl(int a, int b)
        {
            return (double)a * (double)b;
        }

        // 割り算
        static double divide(int a, int b)
        {
            if (b == 0)
            {
                return -9999999999999999999.9;
            }
            else
            {
                return (double)a / (double)b;
            }
        }

        // aのb乗
        static double pow2(int a, int b)
        {
            int result = 1;
            for (int i = 0; i < b; i++)
            {
                result *= a;
            }
            return (double)result;
        }
    }
}

delegate double Culc2(int a, int b);

まずは「Culc2」というメソッドを参照する変数の型が用意されました。具体的には、int型2つを引数に受け取り、結果をdouble型を返すメソッドを当て込むデリゲートの型を用意します。

Culc2 culc2Test = new Culc2(plus);

次に、これは「Culc2」というデリゲートの型のインスタンス「culc2Test」を定義しています。そこにはメソッド「plus」を使え、と指定しています。plusは「static double plus(int a, int b)」で定義されているので、戻り値も引数も型が一致していますので、当て込めます

double testnum = culc2Test(24, 33);

そしてデリゲート型のインスタンスはもちろんメソッドとして呼び出すことができる。ここではplusを当て込んで定義したculc2Testに、24と33を引数に指定してあげています。plusはここでは加算なので、24+33の結果を出すはずです。

実行結果
Hello Ramda!
答えは57です。

「デリゲート型定義→デリゲートのインスタンス作成→呼び出し」が成功しました!!(*˘ᗜ˘*)

ですが、これだと関数を直接呼び出した方が全然いいじゃない!!って、デリゲートの使い道がないので、配列にすることで力を発揮するようなので、今度は配列にしてデリゲートを使ってみます

デリゲートの配列

今度はデリゲートでインスタンスを配列にしてみます(˶ ・ᴗ・ )੭⚐

Program.cs
using System;

namespace MyTrainingCsharp1
{
    // 2値を計算するメソッドデリゲート型の定義
    delegate double Culc2(int a, int b);
    
    class Program
    {
        // メイン関数(プログラム開始)
        static void Main(string[] args)
        {
            Console.WriteLine("Hello Ramda!");

            // 2値計算メソッドデリゲート配列「culc2tests」を定義する。5種類の計算をするため、5要素配列で定義する
            Culc2[] culc2tests = new Culc2[5];
            culc2tests[0] = new Culc2(plus);
            culc2tests[1] = new Culc2(minus);
            culc2tests[2] = new Culc2(mltpl);
            culc2tests[3] = new Culc2(divide);
            culc2tests[4] = new Culc2(pow2);

            // 5種類の計算を表示する
            for (int i = 0; i < 5; i++)
            {
                double result = culc2tests[i](2, 7);
                Console.WriteLine("結果:" + result);
            }
        }

        // 足し算
        static double plus(int a, int b)
        {
            return (double)a + (double)b;
        }

        // 引き算
        static double minus(int a, int b)
        {
            return (double)a - (double)b;
        }

        // 掛け算
        static double mltpl(int a, int b)
        {
            return (double)a * (double)b;
        }

        // 割り算
        static double divide(int a, int b)
        {
            if (b == 0)
            {
                return -9999999999999999999.9;
            }
            else
            {
                return (double)a / (double)b;
            }
        }

        // aのb乗
        static double pow2(int a, int b)
        {
            int result = 1;
            for (int i = 0; i < b; i++)
            {
                result *= a;
            }
            return (double)result;
        }
    }
}

お次はデリゲート型「Culc2」の配列「culc2tests」を定義してみました!!✿.*・最初の初期化はまぁ手作業であまり意味をなさないが…

Culc2[] culc2tests = new Culc2[5];
culc2tests[0] = new Culc2(plus);
culc2tests[1] = new Culc2(minus);
culc2tests[2] = new Culc2(mltpl);
culc2tests[3] = new Culc2(divide);
culc2tests[4] = new Culc2(pow2);
for (int i = 0; i < 5; i++)
{
    double result = culc2tests[i](2, 7);
    Console.WriteLine("結果:" + result);
}

実際にデリゲートのインスタンス配列でループで参照させることによって、いろんなメソッドを当て込んで、それを呼び出すことができるので、イベント動作などには強みが出そうだな…って感じがします

実行結果
Hello Ramda!
結果:9
結果:-5
結果:14
結果:0.2857142857142857
結果:128

この通り!!実行結果もデリゲートのインスタンス配列に紐づいたメソッドが読み込まれていました!!٩(.› ‹.✿)۶

C言語でいう「関数のポインタ」と同じ

C#のデリゲートは、とにかくC言語の「関数のポインタ」と同じようなことが、よくよく感じられるようになったんです。ただし、少し使い方が違いますが…

pointer_func.c
#include<stdio.h>

/* プロトタイプ宣言 */

// 足し算・引き算のプロトタイプ宣言
double plus(int a, int b);
double minus(int a, int b);
// 2値の計算結果を出す関数プロトタイプ宣言
void print_culc2(double (*culc2)(int, int), int a, int b);


/* メイン関数 */
int main(int argc, char *argv[])
{
	int i;
	
	// 関数ポインタの配列「culc2tests」…足し算と引き算の関数をセット
	double (*culc2tests[])(int, int) = { plus, minus };
	
	// 結果を表示
	for (i = 0; i < 2; i++) {
		print_culc2(culc2tests[i], 43, 17);
	}
	
	return 0;
}


/* 計算関数定義 */

// 足し算
double plus(int a, int b)
{
	return (double)a + (double)b;
}
// 引き算
double minus(int a, int b)
{
	return (double)a - (double)b;
}

// 2値の計算結果を出す
void print_culc2(double (*culc2)(int, int), int a, int b)
{
	double result;
	
	result = culc2(a, b);
	printf("計算結果は%.1fです。\n", result);
}

C言語の方は簡単のため、plusとminusの2つしか2値計算の関数は使いませんが、それぞれ比較することそのものは可能なので、比較してみました

C#
delegate double Culc2(int a, int b);
Culc2[] culc2tests = new Culc2[5];
C言語
double (*culc2tests[])(int, int) = { plus, minus };
  • C#:「delegateでメソッド参照の型宣言」→「デリゲートの型で、メソッド本体を参照するインスタンスを宣言」
  • C言語:直接、関数の型を指定して、関数へのポインタを宣言
C#
culc2tests[0] = new Culc2(plus);
C言語
culc2tests[0] = plus;
  • C#:引数と戻り値の型が一致するメソッドの指定して、new演算子でインスタンス作成(「culc2tests[0] = plus;」のように直接メソッド名は指定しない)
  • C言語:引数と戻り値の型が一致する関数名を直接指定
C#
double result = culc2tests[i](2, 7);
C言語
double result = culc2tests[i](2, 7);

関数(メソッド)の呼び出しはC#もC言語も同じように呼び出せる

こんなふうに、C#のデリゲートは、C言語の関数のポインタとして参照していて似ているものだということが実感できました(*´꒳`*)なるほどねぇ~~

匿名メソッドとして、ラムダ式(=>演算子)を代入してみる

さて、C#には匿名メソッドが使えるので、ラムダ式(=>演算子)を使って、メソッド名の定義を省略して、メソッド定義を直接デリゲートのインスタンス作成時に行ってみたんです。

Program.cs
using System;

namespace MyTrainingCsharp1
{
    // 2値を計算するメソッドデリゲート型の定義
    delegate double Culc2(int a, int b);
    
    class Program
    {
        // メイン関数(プログラム開始)
        static void Main(string[] args)
        {
            Console.WriteLine("Hello Ramda!");

            // 2値計算メソッドデリゲート配列「culc2tests」を定義する。6種類の計算をするため、6要素配列で定義する
            Culc2[] culc2tests = new Culc2[6];
            culc2tests[0] = new Culc2(plus);
            culc2tests[1] = new Culc2(minus);
            culc2tests[2] = new Culc2(mltpl);
            culc2tests[3] = new Culc2(divide);
            culc2tests[4] = new Culc2(pow2);

            // デリゲート配列6要素目は、匿名関数(ラムダ式)で、bのaに対する対数関数(log)計算を定義する
            culc2tests[5] = (int a, int b) =>
            {
                // log(b)_base:a を計算する
                return Math.Log((double)b, (double)a);
            };

            // 5種類の計算を表示する
            for (int i = 0; i < 6; i++)
            {
                double result = culc2tests[i](2, 7);
                Console.WriteLine("結果:" + result);
            }
        }

        // 足し算
        static double plus(int a, int b)
        {
            return (double)a + (double)b;
        }

        // 引き算
        static double minus(int a, int b)
        {
            return (double)a - (double)b;
        }

        // 掛け算
        static double mltpl(int a, int b)
        {
            return (double)a * (double)b;
        }

        // 割り算
        static double divide(int a, int b)
        {
            if (b == 0)
            {
                return -9999999999999999999.9;
            }
            else
            {
                return (double)a / (double)b;
            }
        }

        // aのb乗
        static double pow2(int a, int b)
        {
            int result = 1;
            for (int i = 0; i < b; i++)
            {
                result *= a;
            }
            return (double)result;
        }
    }
}

今度はデリゲートのインスタンスを1つ増やして、ラムダ式で匿名メソッド定義でインスタンスを設定したんです

culc2tests[0] = new Culc2(plus);

これはあらかじめ定義されたメソッド「plus」を設定して、デリゲートのインスタンスを作成しています。

culc2tests[5] = (int a, int b) =>
{
    // log(b)_base:a を計算する
    return Math.Log((double)b, (double)a);
};

対照的に「=>」演算子を使って、引数intのaとbを用いて、{ ~ }の内部で、対数関数(基底をaにbの対数を求めている)を計算し、returnを行っています。

そこで私も、正直「ラムダ式」って聞くと頭がスッキリしないときがあったんです。。。( ´ •̥ ̫ •̥ ` )でもラムダ式もメソッドであるという点と、ラムダ式の代入はC言語でいう関数のポインタで参照しているものと同じことを踏まえると、スッキリしました

すなわち、上記のラムダ式は、以下を意味するものと思います

culc2tests[5] = new Culc2(Method001);
  …(中略)
double Method001(int a, int b)
{
    // log(b)_base:a を計算する
    return Math.Log((double)b, (double)a);
};

では、最後に追加したラムダ式のメソッドがちゃんと動いているのでしょうかぁ…

実行結果
Hello Ramda!
結果:9
結果:-5
結果:14
結果:0.2857142857142857
結果:128
結果:2.807354922057604

一番下が対数関数の結果なので、メソッドをしっかり認識できたことがわかりました(˶ ・ᴗ・ )੭⚐⚑

ちなみに、ラムダ式(=>)はreturnのみの1行であれば、こんな風に省略ができるけど、ちょっとわかりづらいかも…( ´ •̥ ̫ •̥ ` )

// 対数関数の値のreturnの1行だけであれば、以下の省略型も可
culc2tests[5] = (int a, int b) => Math.Log((double)b, (double)a);

「<引数> => <返す値>」というズバッと〆たラムダ式ですが…まるで何かを参照しているようでわかりづらいかも。。。

参照文献

C#のメソッド本体の参照(delegate:デリゲート / =>:ラムダ式)

C言語の関数へのポインタ

4
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1