【C#】Actionとは | Actionを定義する方法・使用する方法

C# のコーディングにおいては「delegateデリゲート)」を使えるようになっておくことが重要です。デリゲートは「移譲」という意味を持ち、と処理の実行を別のオブジェクトに移譲(譲る)することが出来ます。

この記事では、デリゲートの中でも戻り値を持たない「Action」について解説します。Actionの定義方法や使い方をサンプルコードを交えて紹介していきます。

C# における Action とは

C#のデリゲートの一種である Action は以下のような特徴を持っています。

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

まずはAction定義を確認しておきます。

public delegate void Action(T obj);

Actionの色々な書き方は後述しますが、複数のパラメータを扱う場合は、以下の書き方のように Action を定義する必要があります。カンマ区切りで後ろに付け加えていくようにします。

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

上記のような場合は第一引数がint型、第二引数がstring型、第三引数がbool型を受けることができます。

Actionを定義するいろいろな書き方

Actionを使った色々な書き記載しておきます。まずは引数のないActionを定義する方法です。左辺を Action と明示的にしていますが、var としても同様に使えます。

//A.デリゲートを使用したAction
Action actionA = new Action(delegate () { Console.WriteLine("ActionA"); });

//B.デリゲートを使用したAction
Action actionB = delegate () { Console.WriteLine("ActionB"); };

//C.ラムダ式を使用したAction
Action actionC = new Action(() => Console.WriteLine("ActionC"));

//D.ラムダ式を使用したAction
Action actionD = () => Console.WriteLine("ActionD");

引数を持つActionを定義する方法は以下の通り。書き方自体は引数がない場合とほとんど変わりません。ラムダ式の書き方にだけ注意すればよいかと思います。

//A.デリゲートを使用したAction
Action<int> actionA = new Action(delegate (int num) { Console.WriteLine($"actionA:{num}"); });

//B.デリゲートを使用したAction
Action<int> actionB = delegate (int num) { Console.WriteLine($"actionB:{num}"); };

//C.ラムダ式を使用したAction
Action<int> actionC = new Action(num => Console.WriteLine($"actionC:{num}"));

//D.ラムダ式を使用したAction
Action<int> actionD = num => Console.WriteLine($"actionD:{num}");

左辺の Action<int> は var で置き換えることが可能で、var を使い場合は引数に関する記載は不要です。

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

それでは実際にActionを使ったアプリケーションを作成していきます。単なる定義だけでなく、少し実践的な使い方を紹介していきます。新規のコンソールアプリケーションを作成して、以下のソースコードを記述して実行してみてください。

using System;
using System.Collections.Generic;

namespace App01
{
    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 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が定義されている箇所をみてみます。実際は「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のサンプルコードを見ていきたいと思います。複数の引数は最大16個まで持つことができますが、一般的には16個も引数を持つことは稀だと思いますので2個くらいにとどめたいと思います。

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

using System;
using System.Collections.Generic;

namespace App02
{
    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を定義する方法でした。サンプルでも解説したので、そこまで難しい内容ではなかったのではないでしょうか。LINQを理解するうえで重要になるので、復習も忘れずにしておいてください。