技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

C#:デリゲートの説明(Action・Func、マルチキャストデリゲート含む)

C#のデリゲートの解説は既に色々と出回っていますが、わかりにくい概念であり、色々な人が異なる切り口で説明した方が良いと思うので、私の方でも記事にします。
 
----------------------------------------
 
デリゲートとは、一言で言えば「関数を変数として扱う」機能です。
個人的には、「1つの関数のみを定義したクラスやインターフェースのようなもの」と捉えた方が分かりやすいと思います。
 
実務ではコールバック(本処理の終わりに特定の終了処理をさせる)をさせたい場合に使用されることが多いようです。
 
以下はサンプルコードです。
理解を助けるためにコメントを入れているので、それもご参照ください。
 
【サンプルコード1】
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateTest
{
    class Program
    {
        // delegateの型「EndFunc」を定義
        public delegate string EndFunc(string s);

        static void Main(string[ ] args)
        {
            // 後述のメソッド「MakeEndMessage」を、
            // 「EndFunc」型の変数「endFunc」に格納
            EndFunc endFunc = MakeEndMessage;

            // クラスのオブジェクトと同じように、
            // 変数として引き渡すことができる。
            SomethingFunc(endFunc);
        }

        static void SomethingFunc(EndFunc endFunc)
        {
            Console.WriteLine("DoSomething");

            // 引き渡された関数を実行
            Console.WriteLine(endFunc("Something"));

            Console.ReadLine();
        }

        // 「EndFunc」型(引数string・戻り値string)の関数
        // 今回delegateする関数
        static string MakeEndMessage(string s)
        {
            return "End" + s;
        }

    }
}
 
【実行結果1】
DoSomething
EndSomething
----------------------------------------
 
引き渡す関数は匿名メソッドやラムダ式でも記述可能であり、これらを利用することで関数の定義を省略することができます。
 
また、引き渡された関数を受け取る際、.Netで用意されている以下のジェネリック定義を使用することができます。
ジェネリック定義を使用することで、デリゲートを省略することができます。
 
・System.Action型
 戻り値がない場合に使用
 Action、Action<引数1>、Action<引数1,引数2>…等
 
・System.Func型
 戻り値がある場合に使用
 Func<戻り値>、Func<引数1,戻り値>、Func<引数1,引数2,戻り値>…等
 
以下は、ラムダ式とSystem.Func型を使用し、サンプルコード1を簡略化する例です。
 
【サンプルコード2】
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateTest
{
    class Program
    {

        static void Main(string[ ] args)
        {
            // こんな感じでラムダ式を用いて、
            // 関数の定義を省略しても良い。
            // (引数) => {処理}
            SomethingFunc( (string s) => {
                return "End" + s;
            });
        }

        // こんな感じでジェネリック定義を使用し、
        // delegateの定義を省略することもできる。
        static void SomethingFunc(Func<string,string> endFunc)
        {
            Console.WriteLine("DoSomething");

            // 引き渡された関数を実行
            Console.WriteLine(endFunc("Something"));

            Console.ReadLine();
        }

    }
}
 
【実行結果2】
DoSomething
EndSomething
----------------------------------------
 
引き渡す関数は、+演算子で追加したり-演算子で削除したりすることもできます。
マルチキャストデリゲート)
 
以下、+演算子や-演算子を試してみた例です。
 
【サンプルコード3】
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateTest
{
    class Program
    {
        // delegateの型「EndFunc」を定義
        public delegate void EndFunc(string s);

        static void Main(string[ ] args)
        {
            EndFunc endFunc;

            // +演算子で実行する関数を追加
            endFunc  = MakeEndMessage;
            endFunc += MakeEndMessage2;
            Console.WriteLine("[Append Func]");
            SomethingFunc(endFunc);

            // -演算子で実行する関数を削除
            endFunc -= MakeEndMessage;
            Console.WriteLine("[Delete Func]");
            SomethingFunc(endFunc);

            Console.ReadLine();
        }

        static void SomethingFunc(EndFunc endFunc)
        {
            Console.WriteLine("DoSomething");

            // 引き渡された関数を実行
            endFunc("Something");
        }

        // 「EndFunc」型(引数string・戻り値無し)の関数
        static void MakeEndMessage(string s)
        {
            Console.WriteLine("End" + s);
        }

        // 「EndFunc」型(引数string・戻り値無し)の関数
        static void MakeEndMessage2(string s)
        {
            Console.WriteLine("End2" + s);
        }

    }
}
 
【実行結果3】
[Append Func]
DoSomething
EndSomething
End2Something
[Delete Func]
DoSomething
End2Something