PG日誌

各記事はブラウザの横幅を1410px以上にすると2カラムの見出しが表示されます。なるべく横に広げてみてください。

C#の配列にForEachメソッドを追加する

C#の配列にForEachメソッドを直接追加する方法を紹介したいと思います。

ForEachメソッドは、Pythonにmap関数とほぼ同じ機能を持ちます。map関数は「map関数は、イテラブルオブジェクトに対しその全ての要素に指定した関数を実行し、結果をシーケンスとして返却します。」という説明で、平たく言うとListとか配列の全ての要素に対し一括で何らかの処理をしますという事です。

C#は同じ機能が「List.Foreach(Action)」として存在しますが、配列にはそのような機能がありません。代わりに「Array.Foreach(Action)」というメソッドがありますが、組み込み型(intやdoublde)だと内容に加算等の操作ができません。

【補足】ForEachメソッドは操作は途中で中断することが出来ません。あくまで最初から最後まで一括操作となります。そういった、「条件次第で処理を中止する必要がある」場合は、foreach構文を使用します。*1あくまでmap関数相当の機能を持つためこ、ForEach中でbreakできないがyieldができない、非同期ができない、などと言うのは全くの見当違いと言えます。

とはいえ、for文やforeach文を使えば実現可能ですがいちいち以下のように毎回コードを書くのは面倒です。また、Array.Foreachに機能的に同様なメソッドが用意されていますが、配列を操作をするために外部クラスを呼び出すのもやや変です。

// この配列内の全ての要素を+1する
var array = new int[] { 1, 2, 3, 4, 5 }

array.ForEach(item => item += 1);
// コンパイルエラー、この操作は存在しない

Array.Foreach(array, item => item += 1) // > 1, 2, 3, 4, 5 :内容が変わらない
// 組み込み型(というか構造体型 : int, byte, long, float etc...)は操作が反映されない!!!

// 結局こう書くしかない=面倒
for(int i = 0; i < array.Length; i++)
{
    array[i] += 1;
}

そこで「拡張メソッド」という機能を使用し配列にForEachメソッドを追加します。

拡張メソッドで配列にForeachを追加する

ArrayExtensionクラスを作成して以下の通り、Foreachメソッドを追加します。

// 配列を拡張するためのクラス
public static class ArrayExtension
{
    // (1) 既存のList.ForEachと同等の機能を持つ拡張メソッド
    public static void ForEach<T>(this T[] array, Action<T> action)
    {
        if (action == null) { throw new ArgumentNullException(nameof(action)); }

        for (int i = 0; i < array.Length; i++)
        {
            action?.Invoke(array[i]);
        }
    }

    // (2) refを使った構造体への作用の反映
    public static void ForEach<T>(this T[] array, RefAction<T> action)
    {
        if (action == null) { throw new ArgumentNullException(nameof(action)); }

        for (int i = 0; i < array.Length; i++)
        {
            action?.Invoke(ref array[i]);
        }
    }

    // (3) ラムダ式の戻り値を使用して作用を反映する
    public static void ForEach<T>(this T[] array, Func<T, T> action, int? a)
    {
        if (action == null) { throw new ArgumentNullException(nameof(action)); }

        for (int i = 0; i < array.Length; i++)
        {
            array[i] = action.Invoke(array[i]);
        }
    }
}

// この宣言も同じところに追加
public delegate void RefAction<T>(ref T item);

使い方は以下の通りです。

public static void Main(string[] args)
{
    string[] strArray = new string[] { "a", "b", "c", "d" };
    strArray.ForEach(str => str + "_asdf");
    strArray.ForEach(str => Console.Write($"{str}, "));
    // > a, b, c, d,
    // ★★★List.ForEachのように構造体には作用できないけど追加した操作

    strArray.ForEach((ref string ss) => ss += "_asdf");
    strArray.ForEach(str => Console.Write($"{str}, "));
    // >  a_asdf, b_asdf, c_asdf, d_asdf,
    // ★★★refを使うラムダ式で変更が反映される!

    strArray.ForEach(ss => { return ss + "_xyz"; });
    strArray.ForEach(str => Console.Write($"{str}, "));
    // > a_asdf_asdf_xyz, b_asdf_asdf_xyz, c_asdf_asdf_xyz, d_asdf_asdf_xyz,
    // ★★★戻り値を指定する版でも値が反映される!
}

これで、配列が構造体や組み込み型のintやdoubleでも変更が反映されるようになりました。

*1:メソッドの名前が悪いですね。