【C#】デリゲートでカプセル化した処理をメソッドの引数にする方法

デリゲートはC#における重要なトピックであるため、当ブログではよく取り上げています。void型のデリゲートは「【C#】デリゲートを定義して使用する方法」にて解説しましたので、今回は戻り値を持つ場合のデリゲートについて解説していきます。

デリゲートで重要になるのは「処理をカプセル化できる」という概念になります。戻り値を持つデリゲートの使い方を知ることは、後々に学んでいく様々なデリゲートにも応用できるので、ぜひとも押さえておきましょう。

戻り値を持つデリゲート

void型のデリゲートを記述する方法と差があるわけではありません。デリゲートの定義部分の戻り値の記述部分が「void」から戻り値の型になるだけです。void型のデリゲートは以下のように記述していました。

delegate void HogeFunc(string str);

上記の場合は戻り値を持たずに、string型の引数を1つとるメソッドを格納できるデリゲートでした。では戻り値を持つ場合のデリゲートのサンプルを、よく使う型でいくつか紹介します。

//bool型の戻り値を持つデリゲート
delegate bool HogeFunc1(string str);

//string型の戻り値を持つデリゲート
delegate string HogeFunc2(string str);

//int型の戻り値を持つデリゲート
delegate int HogeFunc3(int number);

基本的には値型を戻り値とするデリゲートを記述していますが、独自クラスを戻り値に指定してデリゲートを記述することも可能です。

戻り値を持つデリゲートの使い方

それでは戻り値を持つデリゲートの使い方を解説していきます。今回のサンプルはメソッドに引数として設定する場合だけを紹介します。新しいコンソールアプリケーションを作成して、以下のように記述して実行してみましょう。

using System;
using System.Collections.Generic;

namespace App01
{
    class Program
    {
        // デリゲートの定義
        delegate bool CheckMethod(List<int> src, int number);

        // デリゲートの処理詳細部分
        private static bool HasNumberInList(List<int> src, int number)
        {
            if(src is null || src.Count <= 0)
            {
                //データソースが値を持たない場合
                return false;
            }

            foreach(var item in src)
            {
                if(item.Equals(number))
                {
                    //引数の数字と同一だったらTrueにする
                    return true;
                }
            }
            return false;
        }

        // デリゲートの実行箇所
        private static void Executer(CheckMethod method, List<int> src, int number)
        {
            string message;
            if(method(src, number))
            {
                message = "入力された数値が存在します。";
            }
            else
            {
                message = "入力された数値は存在しません。";
            }
            Console.WriteLine(message);
        }

        // メインの実行部分
        static void Main(string[] args)
        {
            var list = new List<int>()
            {
                1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
            };

            //数字以外を入力したら例外になりますが、今回は割愛します
            Console.WriteLine("数値を入力してください。");
            int input = Convert.ToInt32(Console.ReadLine());

            //実行処理
            Executer(HasNumberInList, list, input);
            Console.ReadLine();
        }
    }
}

かなりソースコードが長くなっていますが順を追って解説していきます。まずはデリゲートの宣言部分である以下の箇所をみてみましょう。

// デリゲートの定義
delegate bool CheckMethod(List<int> src, int number);

今回はbool型を戻り値として、intのListとintを引数に持つデリゲートを宣言してみました。名前を「CheckMethod」としている通り、引数を判定するようなメソッドを想定してた命名となっています。この定義したデリゲートの実処理にあたる部分が「HasNumberInList」のメソッドです。

// デリゲートの処理詳細部分
private static bool HasNumberInList(List<int> src, int number)
{
    if(src is null || src.Count <= 0)
    {
        //データソースが値を持たない場合
        return false;
    }

    foreach(var item in src)
    {
        if(item.Equals(number))
        {
            //引数の数字と同一だったらTrueにする
            return true;
        }
    }
    return false;
}

このメソッドは引数として渡されたリストの要素に第二引数の数字が存在していたら真(true)を返却し、存在しなければ偽(false)を返すという処理内容です。この処理をメソッドの外側から引き渡してメソッド内で使用します。

// デリゲートの実行箇所
private static void Executer(CheckMethod method, List<int> src, int number)
{
    string message;
    if(method(src, number))
    {
        message = "入力された数値が存在します。";
    }
    else
    {
        message = "入力された数値は存在しません。";
    }
    Console.WriteLine(message);
}

先ほど定義した「HasNumberInList」メソッドは「Executer」メソッドにて引き受けて実行します。第一引数として「CheckMethod」を引き受け、第二引数をチェック対象のリスト、第三引数を検知したい番号としています。「Executer」メソッドの中では、渡された3つの引数を使って判定を行うような処理としています。

if(method(src, number))
{
    message = "入力された数値が存在します。";
}
else
{
    message = "入力された数値は存在しません。";
}

if文を使って第一引数の「method」メソッドの戻り値によってメッセージの出し分けを行っています。引数で渡されている「method」は、呼び出し元から「HasNumberInList」が渡されているので、こちらの処理が起動されます。

このように、デリゲートをメソッドの引数として外側から設定することで、外部に依存した形式で処理ロジックを記述することができます。例えばメインの処理ルーチンが同一なのに、データの種類によって判定方法が異なる場合などは、デリゲートで判定処理を記述しておいて、データによって引数とする判定処理を変えるなどの方法が活用できます。

このような「処理を外側から渡して処理に柔軟性を持たせる」というのも、ラムダ式以降にも大きく影響します考え方ですので、少しは慣れておくと今後の学習もスムーズに進むと思います。ぜひとも復習しておいてください。