C#のList<T>.ForEachでTが構造体の値を更新する

前回、C#の配列にForEachメソッドを追加すると同じく、ListのTが構造体の場合、ForEachメソッド内で引数に対する変更がListへ反映されないため、拡張メソッドの機能を利用して、変更が反映される操作を追加します。

具体的にどういう状況か以下のコード(もしくは先述の記事)を参照。

public static void Main(string[] args)
{
    var list = new List<string>() { "a", "b", "c", "d" };
    list.ForEach(str => str + "_asdf");
    // > a, b, c, d,
    // ★★★List.ForEachはTが構造体だと変更が作用しない
}

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

新しく、ListExtensionクラスを作成して操作を追加します。

// Listを拡張するためのクラス
public static class ListExtension
{
    // (1) refを使った構造体への作用の反映
    public static void ForEach<T>(this IList<T> list, RefAction<T> action)
    {
        if (action == null) { throw new ArgumentNullException(nameof(action)); }

        for (int i = 0; i < list.Count; i++)
        {
            // インデクサはref渡せない
            T item = list[i];
            action?.Invoke(ref item);
            list[i] = item;
        }
    }

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

        for (int i = 0; i < list.Count; i++)
        {
            list[i] = action(list[i]);
        }
    }

}

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

使い方は以下の通り。

public static void Main(string[] args)
{
    list.ForEach((ref string ss) => ss += "_asdf");
    // 中身: a_asdf, b_asdf, c_asdf, d_asdf,
    // ★★★refを使うラムダ式で変更が反映される!

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

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