C# ゼロから理解するラムダ式:C#でActionを書く方法 #11

前回はデリゲートからの派生形である「Predicate」を学習しました。まだ確認していないならば「C# ゼロから理解するラムダ式:C#でPredicateを書く方法 #10」を確認して、前回の内容も併せて確認してもらえたらよいかなと思います。

今回は「Predicate」の仲間である「Action」について学びます。前回の「Predicate」は戻り値がbool型でしたが、今回の「Action」は戻り値を持たないvoid型になります。実際のサンプルコードも紹介しながら解説していきます。

Actionとは

実際のサンプルソースの前にActionの特徴から確認していきましょう。Actionは以下の特徴を持っているC#の機能となります。

  • 戻り値を持たないvoid型である
  • Genericで引数の型を指定できる
  • パラメータは最大16個まで設定できる

例えばint型の引数を1つだけ受ける場合は以下のように記述します。

Action<int> HogeAction = ...

また、パラメータを複数指定する場合はカンマ区切りで後ろに付け加えていくだけです。また違う型で引数を受け取ることも可能になります。

Action<int, string, bool> HogeAction = ...

上記のような場合は第一引数がint型、第二引数がstring型、第三引数がbool型を受けることができます。ここまでがActionの基本事項となります。次からが実践的な内容となっていきます。

C#でActionを書く方法(単一引数)

それでは実際にActionを使ったアプリケーションを作成していきたいと思います。新規のコンソールアプリケーションを作成して以下のソースコードを記述して実行してみてください。

using System;
using System.Collections.Generic;

namespace App10
{
    class Program
    {
        static void Main(string[] args)
        {
            //リストを作成する
            var list = new List()
            {
                1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
            };

            //メソッドに処理を渡して実行する
            WriteNumbers(x => 
                Console.WriteLine($"数値は{ x }です。"),
                list);

            Console.ReadLine();
        }

        private static void WriteNumbers(Action<int> action,
            List storage)
        {
            foreach(int item in storage)
            {
                //渡されたactionを実行する
                action(item);
            }
        }
    }
}

上記のアプリケーションのうち、Actionを定義している箇所は以下の箇所になります。int型の引数を一つ受け取れるActionを定義しています。

private static void WriteNumbers(Action action, List storage)

第二引数は単純にint型のリストになります。第一引数のほうがActionなのは見てわかる通りです。ではこの第一引数が使用されているメソッド内をみてみます。

foreach(int item in storage)
{
    //渡されたactionを実行する
    action(item);
}

第二引数で受け取ったリストをforeachで回しつつ、第一引数であるActionを実行しています。第二引数はint型のリストですので、各要素はint型になります。よって「action(item);」が成り立ちます。また、void型がActionの決まりですので戻り値はありません。

では、このActionが定義されている箇所をみてみます。実際は「WriteNumbers」メソッドの呼び出し元にラムダで記述しています。対応するのは以下の箇所です。

//メソッドに処理を渡して実行する
WriteNumbers(x => 
    Console.WriteLine($"数値は{ x }です。"),
    list);

横に長くなってしまい見づらいので引数を改行しています。ラムダ式だけを抜き取ると以下のようになります。

x => Console.WriteLine($"数値は{ x }です。")

第一引数のActionはint型の引数を1つ取ることができます。ラムダ式は「(引数) => (式)」で表すことができ、引数が一つの場合は括弧を省略できるので「x => (式)」としています。

また、上記のラムダ式は処理部分の内容が「Console.WriteLine($”数値は{ x }です。”)」となっており、引数をそのままコンソール画面に出力するだけの処理をしています。

なお、この引数である「x」は「WriteNumbers」メソッド内の「action(item);」と記述された「item」が該当します。このitemはint型のリスト(storage)の各要素なのでint型の値です。具体的には「1, 2, 3, 4, 5, 6, 7, 8, 9, 10,」の各要素です。

なお匿名メソッドでも記述できますので、以下を参考までに掲載しておきます。Predicateでもそうでしたが、Actionもデリゲートのテンプレートみたいなものなので匿名メソッドで記述可能です。

WriteNumbers(delegate (int x)
    {
        Console.WriteLine($"数値は{ x }です。");
    },
    list);

C#でActionを書く方法(複数引数)

先ほどは単一の引数の場合のActionについて学びました。次は複数の引数を持つ場合のActionのサンプルコードを見ていきたいと思います。Actionでは最大16個までの引数を持つことができますが、一般的には16個も引数を持つことは稀だと思われるので、今回のサンプルでは2個の引数にとどめます。

では、新規のコンソールアプリケーションを作成して以下のソースコードを記述してみてください。記述ができたら、実際に動かしてみて挙動を確認して動きを考えてみてください。

using System;
using System.Collections.Generic;

namespace App11
{
    class Program
    {
        static void Main(string[] args)
        {
            //処理で使用する値を取得する
            Console.WriteLine("数値を入力してください。");
            int input = Convert.ToInt32(Console.ReadLine());
            List storage = new List()
            {
                1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
            };

            //メソッドを実行する
            WriteNumbers((src, num) =>
                {
                    foreach(var item in src)
                    {
                        if(item.Equals(num))
                        {
                            Console.WriteLine("入力値は存在します。");
                            return;
                        }
                    }
                    Console.WriteLine("入力値は存在しません。");
                },
                storage,
                input);
            Console.ReadLine();
        }

        //Actionを起動させるメソッド
        private static void WriteNumbers(Action<List, int> action,
                                         List storage,
                                         int input)
        {
            //第一引数と第二引数を渡して処理を実行する
            action(storage, input);
        }
    }
}

前の章で詳しく解説しているので、ここでは詳細な説明を割愛して、簡単な説明のみにとどめたいと思います。Actionを定義しているのは「WriteNumbers」メソッドになります。

//Actionを起動させるメソッド
private static void WriteNumbers(Action<List, int> action,
                                 List storage,
                                 int input)

引数を三つ取得することができ、一つ目がActionの処理、二つ目がActionに渡す引数の一つ目、また第三引数がActionに渡す二つ目の引数となります。処理内部では「action(storage, input);」と引数を渡して実行するだけです。

なお、複数の引数を持つ場合は「Action<List, int> action」と引数の型をカンマ区切りで設定することができます。今回はint型のリストとint型の数値を受け取るようにしています。また、Actionを定義しているのは以下の箇所になります。

//メソッドを実行する
WriteNumbers((src, num) =>
    {
        foreach(var item in src)
        {
            if(item.Equals(num))
            {
                Console.WriteLine("入力値は存在します。");
                return;
            }
        }
        Console.WriteLine("入力値は存在しません。");
    },
    storage,
    input);

WriteNumbersメソッドでは第一引数がActionであり、処理内容になります。今回は複数行を記述できるラムダ式の形式で記載しています。引数は「(src, num)」としていますが、これは「WriteNumbers」メソッドの「action(storage, input);」に該当します。一つ目がint型のリスト、二つ目の引数がint型の数値でした。

以上、Actionについて解説してきました。難しい内容ではないので理解しやすかったのではないかと思います。Predicateに引き続き、このActionも重要な内容なのでしっかりと復習をしておきましょう。

次回は「Func」に取り組んでデリゲートに関連する連載は終了したいと思います。Predicate、Action、そしてFuncの学習が完了したらLINQについて簡単に学びますので、連載もいよいよ佳境に入ってきました。もうひと頑張りです。