本文へスキップ
BecomeCoder

C#オブジェクト指向コース · 第4章 ジェネリクスとデリゲート · レッスン29

デリゲート

ブラウザで完結

導入

これまで変数には、数値や文字列という「データ」を入れてきました。では「処理(メソッド)そのもの」を変数に入れられたら、何が嬉しいのでしょうか?

たとえば「割引の計算方法を後から選べるようにする」「ボタンが押されたときに実行する処理を、あらかじめ渡しておく」——つまり「どの処理を実行するかを後から決める/外から渡す」ことができるようになります。それを可能にするのがデリゲートです。

delegateは英語で「委任する」の意味。「どのメソッドを呼ぶか」を変数に委ねると考えると分かりやすいです。テレビのリモコンのように、同じ「実行」ボタンでも、向ける先(メソッド)を差し替えれば動作が変わります。

図解

flowchart TB
    subgraph 通常の変数
        V1["int x = 5"] --> D1["中身は『数値』"]
    end
    subgraph デリゲート変数
        V2["Operation op = Add"] --> D2["中身は『処理(メソッド)』"]
        D2 -->|opを呼ぶと| R2["Add が実行される"]
    end
    style D2 fill:#e1f5fe

サンプル1: メソッドを変数に入れて呼ぶ

まず「デリゲート型」を用意します。これは「どんな形のメソッドを入れられるか引数と戻り値の型)」を決める“型の設計図”です。下のコードは意味の順に ①→②→③ と読んでください(C#では型宣言はコードより後ろに置く決まりなので、①だけ末尾にあります)。

// ② デリゲート型 Operation の変数に、形の合うメソッド Add を代入する
//    (代入するときは Add。() を付けない=ここでは呼び出さない)
Operation op = Add;

// ③ 変数を関数のように呼ぶと、中に入れたメソッドが実行される
Console.WriteLine(op(3, 4));   // Add(3, 4) が動いて 7

// 形が同じ(int, int → int)なら、別のメソッドに差し替えられる
op = Multiply;
Console.WriteLine(op(3, 4));   // Multiply(3, 4) が動いて 12

int Add(int a, int b) => a + b;
int Multiply(int a, int b) => a * b;

// ① デリゲート型の宣言=「int を2つ受け取り int を返すメソッド」を入れられる型。
//    これは『型の設計図』であって、まだ変数でも処理でもない点に注意。
delegate int Operation(int a, int b);
  • delegate 戻り値の型 型名(引数リスト); で「処理を入れられる型」を定義する(①)
  • 代入は Add(カッコなし)Add() と書くと「呼び出した“結果”」になってしまう(②)
  • op(3, 4) のように変数を呼ぶと、入っているメソッドが動く(③)
  • 入れられるのは「形(引数と戻り値の型)が一致するメソッドだけ」。これが“型”の役目

サンプル2: 「処理」を別のメソッドに渡す(本当の使いどころ)

デリゲートの真価は、「処理を引数として別のメソッドに渡せる」ことです。「何をするか」を呼び出し側が決められます。

// 呼び出し側が「どう計算するか」を渡す
Calculate(10, 3, Add);        // 結果: 13
Calculate(10, 3, Subtract);   // 結果: 7

// Calculate は「計算の手順」だけを持ち、具体的な計算方法は引数で受け取る
void Calculate(int a, int b, Operation operation)
{
    int result = operation(a, b);   // 渡された処理を実行するだけ。中身は知らない
    Console.WriteLine($"結果: {result}");
}

int Add(int a, int b) => a + b;
int Subtract(int a, int b) => a - b;

delegate int Operation(int a, int b);
  • Calculate は「計算の手順」だけを持ち、「具体的な計算方法」を外から受け取る
  • 同じ Calculate が、渡すメソッド次第で足し算にも引き算にもなる
  • この「処理を渡す」考え方は、並べ替えの比較方法・ボタンのクリック処理・LINQなど、いたる所で使われる

補足(覚えておくと得)

実務で自分から delegate を宣言することはまれで、次のレッスンで学ぶ FuncAction(あらかじめ用意された汎用デリゲート)を使うのが普通です。ただし「処理を変数に入れて渡せる」というこの考え方がすべての土台なので、ここを理解しておくことが何より大切です。

演習

// TODO: string を1つ受け取り string を返すデリゲート型 Formatter を定義してください。
//       次に「文字列を大文字にして末尾に "!" を付ける」メソッド Shout を作り、
//       Formatter 型の変数に入れて "hello" を渡し、"HELLO!" と出力してください。
//       (型宣言 delegate ... はコードの末尾に置くこと)
___
  • 期待される出力: HELLO!
ヒント1を見る

型は delegate string Formatter(string s);、メソッドは string Shout(string s) => s.ToUpper() + "!";

ヒント2を見る

Formatter format = Shout; Console.WriteLine(format("hello")); string Shout(string s) => s.ToUpper() + "!"; delegate string Formatter(string s);

まとめ

  • デリゲートは「処理(メソッド)を入れられる変数」の型。delegate 戻り値 型名(引数); で定義する
  • 代入は Add(カッコなし)、呼び出しは op(...)。形が合うメソッドだけ入れられる
  • 「処理を引数として渡す」と、手順は同じで中身だけ差し替えできる(実務では次章の Func/Action を使う)

次回: 定番の汎用デリゲート「Func と Action」です。

実際に動かしてみよう

本文のサンプルや演習のコードは、コードブロック右上の「コピー」ボタンでコピーして、下のエディタに貼り付ければそのまま実行できます。

C# — ブラウザ内で実行

ブラウザ内でC#を動かす環境を読み込みます(初回のみ数秒)。
スクロールして表示された時点でも自動で読み込まれます。