C# で高階関数的な、関数ポインタみたいな(Func でメソッドを切り替える)

ひさしぶり。


こういうことってよくあると思います。

  前処理 hoge
  処理 A
  後処理 fuga

  前処理 hoge
  処理 B
  後処理 fuga

  前処理 hoge
  処理 B
  後処理 fuga

それぞれ前処理と後処理は同じことをやっている、というものです。プログラミングの基本として、共通する部分は関数としてまとめる、というのがありますが、この場合処理の中身が異なるのでそういうわけにもいきません。

関数型言語高階関数というのがあります。(高階関数 - Wikipedia) まさしくやりたいのはこういうこと。関数型言語では引数に関数を渡すことが簡単にできると。
C言語の例が載っています。関数ポインタで実現させているんですね。

C# で関数を引数とする、で思いつくのがデリゲート。これを使えば実現できそうです。
でも個人的にはデリゲートだと宣言の部分などが冗長という印象があって、そこまでやること無いか、と日和ってしまいます。


そこで、C# 3.0 の Func というのを使います。

使用前

    static void Main(string[] args)
    {
        Console.WriteLine("前処理");
        Console.WriteLine("{0}", add(2, 3));
        Console.WriteLine("後処理");

        Console.WriteLine("前処理");
        Console.WriteLine("{0}", mul(2, 3));
        Console.WriteLine("後処理");
    }

    private static int add(int x, int y)
    {
        return x + y;
    }

    private static int mul(int x, int y)
    {
        return x * y;
    }

使用後

    static void Main(string[] args)
    {
        process(add);
        process(mul);
    }

    private static int add(int x, int y)
    {
        return x + y;
    }

    private static int mul(int x, int y)
    {
        return x * y;
    }

    private static void process(Func<int, int, int> calc)
    {
        Console.WriteLine("前処理");
        Console.WriteLine(calc(2, 3));
        Console.WriteLine("後処理");
    }

新たに、process というメソッドを追加しました。これは、
1. 前処理を行う
2. 引数で指定させたメソッドを実行する
3. 後処理を行う
ことを行います。使用前のソースでは2カ所に分散していた前処理と後処理をまとめたことで、もし前処理、または後処理に変更が生じても一カ所の修正ですむのでミスも減ります。
一番大事なのが、 2の「引数で指定されたメソッドを実行する」です。calc(2, 3) とありますが、これでadd、mulが実行されるのです、あら不思議。process メソッドの宣言部を見てみましょう。

    Func<int, int, int> calc

MSDN によると Func とは、
1 つのパラメータを受け取って TResult パラメータに指定された型の値を返すメソッドをカプセル化します。

    public delegate TResult Func<T, TResult>(T arg)

とのことで、これだけだと何のことやらですが。

つまり、Func calc は、
「int 型の引数を2つ受け取って、戻り値が int 型であるというメソッド calc の宣言を宣言する。」となります。
Func の後に int が3つ並んでいますが、最後の int がTResult、すなわち戻り値を意味しています。
あとは、main の部分から、

        process(add);
        process(mul);

と、切り替えたいメソッド(add、mul)を渡してあげることで見事に切り替わってくれる、と。


シンプルですし、実務的にも役立つ場面あるのではないかと。

後書き

実行したいメソッドを切り替えたい、という意味ではデザインパターンのStrategyパターンがふとよぎったり。
あれは継承(またはインターフェース)によって実現させていますが、C# 3.0ではFuncを使うというのもありなのかな、とか。(いや、やんないけど。)
Generics によって、intの引数を2つ持ち、戻り値が int であればなんでもcalcに渡せるわけで、インターフェース継承などよりもより柔軟なことができるんだな−、など今さら Generics の力を思い知る。