[C#] 24. ラムダ式(匿名関数)とAction、Func関数を使い方、そしてクロージャ(Closure)


Study / C#    作成日付 : 2019/07/16 00:36:19   修正日付 : 2021/09/07 20:47:10

こんにちは。明月です。


この投稿はC#でラムダ式(匿名関数)とAction、Func関数を使い方、そしてクロージャ(Closure)に関する説明です。


以前の投稿でデリゲート(delegate)に関して説明したことがあります。

link - [C#] 23. デリゲート(delegate)


デリゲートとは関数ポインタで関数をインスタンスのポインタみたいに管理ができるような機能ということです。つまり、関数をポインタで管理ができればもっと考えると関数の名前が要らずに関数を作成することができます。

ラムダ式とは匿名関数の意味とおりに関数名せずに作成が可能な関数です。

using System;

namespace Example
{
  // クラス生成
  class Program
  {
    // デリゲート宣言
    private delegate void Print(string val);
    // デリゲートリスト
    private static Print list;
    // 実行関数
    static void Main(string[] args)
    {
      // 匿名関数追加
      list += (val) =>
      {
        // コンソール出力
        Console.WriteLine("Lambda1 " + val.ToString());
      };
      // 匿名関数追加
      list += (val) =>
      {
        // コンソール出力
        Console.WriteLine("Lambda2 " + val.ToString());
      };
      // デリゲート実行
      list("Hello world");
      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


上の例をみれば返却式はなく、Stringのパラメータを受け取るデリゲートを作成しました。そしてMain関数で(val) => {}の形式のラムダ(匿名関数)を作ってデリゲートリストに追加しました。

ここでvalのデータタイプはデリゲートで宣言したstringのタイプのデータになり、返却タイプはないのでreturnは要りません。

そして中括弧({})のスタック領域で関数が呼び出したら実行するプログラムのコードを実装します。


実行すればlistのデリゲートを実行して登録されて二つのラムダ式が実行することを確認できます。


そうならこのラムダ式を使うためにはそれを合わせてデリゲートを常に宣言するべきだと思いますが、Func関数とAction関数を利用すればデリゲートが要りません。

using System;

namespace Example
{
  // クラス生成
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // 匿名関数、最後のパラメータは返却タイプ
      Func<int, string> func = (val) =>
      {
        // intタイプの値を受け取って100を掛ける。
        int ret = val * 100;
        // stringタイプで返却する。
        return ret.ToString();
      };
      // 匿名関数、返却値がなし
      Action<string> action = (val) =>
      {
        // コンソール出力
        Console.WriteLine("Action = " + val);
      };
      // 匿名関数funcを呼び出す。 -> パラメータはintタイプを受け取って返却値はstringタイプ
      string data = func(10);
      // 匿名関数actionを呼び出す。 -> パラメータstringタイプを渡す。
      action(data);

      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


上の例では匿名関数を作成しましたが、delegateを宣言せずにFuncとAction関数を使いました。

Func関数は返却値がある匿名関数で、Action関数は返却値がない匿名関数です。

Func関数の最後のパラメータは返却値のデータタイプです。その以外は順番とおりのパラメータのデータタイプです。


ラムダ式というのは実は文法規約を破壊する方法です。ラムダ式が使うことも楽だしデリゲート(delegate)やリストとFunc、Actionを適当に混ぜて作成するならプログラムを実装する時に凄く楽になります。

問題はラムダ式が多くなるとソースは凄く複雑になるし、可読性が凄く悪くなる欠点があります。そのため、一般関数を使うことができれば関数名を作成してラムダ式を使うことを抑える方が良いです。


そうならラムダ式をいつ使うほうが良いか?デザインパターンのオブザーバーパターンを実装する時、つまり、イベントやコールバック関数を実装する時に使う方が良いです。

using System;

namespace Example
{
  // クラス生成
  class Program
  {
    // 出力関数、Action関数はコールバック関数で関数が終わるタイミングで呼び出す。
    // パラメータを入れなかったらnullになる。
    static void Print(string data, Action<string> cb = null)
    {
      // パラメータdataの値に文字列を追加
      string ret = "Print " + data;
      // コンソール出力
      Console.WriteLine(ret);
      // コールバック関数がnullではなければ
      if (cb != null)
      {
        // コールバック関数を呼び出す。
        cb(ret);
      }
    }
    // 実行関数
    static void Main(string[] args)
    {
      // Print関数呼び出す。
      Print("Test");
      // 改行
      Console.WriteLine();
      // Print関数を呼び出す、関数処理が終わるとコールバック関数が呼び出す。
      Print("Hello world", (val) =>
      {
        // データを受け取って文字列を追加
        string ret = val + " Call back!";
        // コンソール出力
        Console.WriteLine(ret);
      });

      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


上の例はデザインのオブザーバーパターンです。javascriptコードでよく見えるコールバック関数です。

オブジェクト指向プログラミング(OOP)でデータをオブジェクト(Object)化する時、必要なコールバックタイプやイベントタイプ、つまり、特定な関数が呼び出した時に呼び出される形式で作成することの意味です。

イベントはeventキーワードを説明する時に詳細に説明します。


基本的にオブザーバーパターンを設計する時、ラムダ式を使うがその以外はクロージャ(Closure)機能を使うために実装します。

using System;

namespace Example
{
  // クラス生成
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // 変数生成
      string val = "Hello world";
      // 匿名関数生成
      Action action = () =>
      {
        // コンソール出力
        Console.WriteLine(val);
      };

      // 関数呼び出す。
      action();

      // 任意のキーを押してください。
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


上の例はMain関数でAction関数を使って匿名関数を作成しました。でも、この匿名関数はパラメータがありません。

そうなのにMain関数で生成したstring valの値を持ってきて使うことができます。もし、これが匿名関数ではなく、一般関数ならとんでもない実装です。なぜなら、スタック領域が全然違う領域だからです。


でも、このラムダ式はソース上のすぐ上のスタック領域の値を持ってくることができます。もちろん、持ってきて修正もできます。これがクロージャ(Closure)機能です。

こんな機能は以外に関数の複雑度をずいぶん減らすことができます。つまり、パラメータで渡すべきなデータあるいはメンバー変数を使うべきな値をローカル変数でクロージャ(Closure)機能で単純なソースコードを作成することができます。

しかし、これが作成しすぎなら可読性が悪くなるので、状況に合わせて、仕様に合わせて作成する方が良いです。


ここまでC#でラムダ式(匿名関数)とAction、Func関数を使い方、そしてクロージャ(Closure)に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

#C#
最新投稿