【C#】デリゲートに関する基礎知識を一気に解説

C#に限らずプログラミングでは「処理を委任する」という考え方があります。これを一般的に「デリゲート」と呼んでおり、現代プログラミングでは非常に重要な役割となっています。

この記事では「デリゲート」について基本的な内容をまとめながら、その使用方法を簡単に解説します。ラムダ(LINQ)についてはとても奥が深いので、「デリゲートなんてあったな」くらいで覚えてもらえればOKです。

デリゲートとは

デリゲートとは「委任・委譲」という言葉を表す単語ですが、これがプログラミング上で何を表すのか、についてまずは考えていきます。

まず前提条件として、デリゲートの表向きの機能は「処理をカプセル化して参照可能にする」という点にあります。カプセル化とは「包む」「閉じる」という意味で、C#では要するに「値のように受け渡せる」ということです。

ここを起点に委任や委譲について考えます。委任とは「人に委ねる」という意味ですが、特定の仕事の一部を誰かに任せるということです。資料の原案を部下に頼む、上司にチェックをお願いする、などなど。

「外部に処理を任せる」というイメージをプログラミング風に言い換えると、「外部に定義した処理を使用する」となります。処理を行う場所以外に記述した処理を使うということです。「処理を引数としてメソッドに渡す」という使用法が一番多いですね。

まとめると「デリゲート」とは「処理を変数のような形式にすることで、引数として他の処理に渡したりできる」ことになります。

デリゲートの使い方

実際にデリゲートを使う方法を、サンプルコードを交えながら見ていくことにします。

using System;

namespace App01
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("名前を入力してください。");
            string name = Console.ReadLine();

            //デリゲートで受け取るメソッドを呼び出す
            Output(WriteHello, name);
        }

        //デリゲートの型
        private delegate void WriteString(string s);

        //デリゲートを使用するクラス
        static void Output(WriteString method, string s)
        {
            method(s);
        }

        //デリゲートで起動したい処理
        static void WriteHello(string s)
        {
            Console.WriteLine("Hello, " + s);
        }
    }
}

それでは以下、簡単にデリゲートを使用する方法を解説していきます。

デリゲートを宣言する

デリゲートを使用するにはデリゲートを定義する必要があります。以下の箇所でデリゲートを定義してメソッドを変数のように使用できるようになります。

//デリゲートの型
private delegate void WriteString(string s);

デリゲートは処理を変数のように使用できますが、「どんな形式の処理なのか」は事前に定義する必要があるのです。上記ではvoid型でstring型の引数を一つ取るメソッドはすべてWriteStringでまとめることができます。

デリゲートの処理を定義する

デリゲートの型が定義できたら、つぎはデリゲートで実行したい処理本体を記述する必要があります。外側だけ作成をしても、実体が伴っていなければ「実行する処理がない」となってしまうからですね。

デリゲート処理を定義する場合は、デリゲートで宣言した形式と同様の形を取るメソッドにする必要があります。つまり、void型でstring型の引数を一つ取るメソッドを定義する必要があるということになります。

//デリゲートで起動したい処理
static void WriteHello(string s)
{
    Console.WriteLine("Hello, " + s);
}

今回の例では単純に渡された引数に文字列をくっつけて、意味のある文言として画面に表示させるだけの簡単な処理にしてみました。デリゲートで定義しているvoid型で、string型の引数を一つ取るメソッドになっていますね。

デリゲートで処理を受けとる

デリゲートにおける最大のメリットは「処理を変数のようにできる」ことでした。それを最大限に利用できるのが「メリットの引数として処理を受け取る」ことになります。それを表しているのが以下の箇所になります。

//デリゲートを使用するクラス
static void Output(WriteString method, string s)
{
    method(s);
}

第一引数の「型」になっている「WriteString」はデリゲートとして定義しています。「処理を変数のように受け取れる」というのは、デリゲートで宣言したものを「型」のようにして受け取ることができる、ということです。

//デリゲートで受け取るメソッドを呼び出す
Output(WriteHello, name);

Mainメソッド内の上記の記述では「WriteHello」メソッドを渡しています。この「WriteHello」メソッドは「WriteString」のデリゲートの型と同じ「メソッド」でした。このようにデリゲートを使うことで「処理自身」をメソッドとして渡すことが可能となります。

デリゲートの良いところ

以上のように、デリゲートは「処理」それ自体をカプセル化して「変数」のように扱うことのできる技術です。この技術を応用することで、より柔軟なプログラミングをすることができます。

とはいえ、現状、デリゲートだけでは「使い勝手が良い」とは言い切れません。なぜなら、宣言したデリゲートは「型のように使える」だけであり、必要な処理自体は必要な分だけ定義しなくてはならないからです。

とはいえ、「処理を変数のように扱う」という発想はC#というプログラミング言語を大きく飛躍させることになります。このデリゲートから進化したのがC#の目玉機能である「LINQ」になります。

まずはデリゲートの理解を

実は意図があって、今はあまり使われていない(廃れている)デリゲートについて取り上げました。なぜならば、最も基本である「デリゲート」が理解できていなければ「処理の委譲」をメインとする「LINQ」が理解できないからです。

「LINQ」はC#の目玉機能です。しかしその一方で「LINQ」に苦手意識を持っている人も多いのが現実です。それは「デリゲート」の基礎が分かっていないからです。C#の便利な機能は「デリゲート」から派生している機能です。