rksoftware

Visual Studio とか C# とかが好きです

.NET 9 Preview 1 で追加された LINQ メソッドの一つ、AggregateBy を書いてみる

.NET 9 Preview 1 で追加された LINQ メソッドを書いてみています。
rksoftware.hatenablog.com

その中で前回、.AggregateBy でちょっと特殊な使い方をしたくて迷っていました。今回はそこを少し深く語ってみます。

■ これまで .Aggregate でよくやっていたこと

ループのそれまでの処理を踏まえる複雑な処理で list へ Add するようなコードを書きたい場合、次のようなコードが結構あると思います。

char[] t = ['a', 'b', 'a', 'a', 'b', 'c'];

{
    List<char> list = new ();
    foreach (var c in t)
    {   // ここが list の現在の状態を参照したりなど、複雑な処理だったりするとき
        list.Add(c);
    }

    // a, b, a, a, b, c
    Console.WriteLine(string.Join(", ", list));
}

そこで私は次のようなコードを書いてしまっていました。

char[] t = ['a', 'b', 'a', 'a', 'b', 'c'];

{
    // こんな風にシュッと書いてしまいたい
    List<char> list = t.Aggregate(new List<char>(), (list, c) => { /* ここが list の現在の状態を参照したりなど、複雑な処理の想定 */ list.Add(c); return list; });

    // a, b, a, a, b, c
    Console.WriteLine(string.Join(", ", list));
}

.AggregateBy の場合

基本的にはこういう値型で使うのだと思います。

char[] t = ['a', 'b', 'a', 'a', 'b', 'c'];

{// 値型を渡すのが本来だと思う
    IEnumerable<KeyValuePair<char, string>> m = t.AggregateBy(x => x, "", (s, x) => x + s);

    // a: aaa, b: bb, c: c
    Console.WriteLine(string.Join(", ", m.Select(x => $"{x.Key}: {x.Value}")));
}

ここで何も考えずに私がよく書いてしまう List<T> に足していくコードを書いた場合には、意図通りではない結果になります。でもこれが仕様通りだと思います。

char[] t = ['a', 'b', 'a', 'a', 'b', 'c'];

{// キーが違っても同じ List<char> が渡ってしまう
    IEnumerable<KeyValuePair<char, List<char>>> m = t.AggregateBy(x => x, new List<char>(), (s, x) => { s.Add(x); return s; });

    // a: abaabc, b: abaabc, c: abaabc
    Console.WriteLine(string.Join(", ", m.Select(x => $"{x.Key}: {string.Join("", x.Value)}")));
}

でも、諦めきれないので考えました。しかしこんなコードいいのか迷います。

char[] t = ['a', 'b', 'a', 'a', 'b', 'c'];

{// こうすれば意図通りだが、こういうので許されるのか......?
    IEnumerable<KeyValuePair<char, List<char>>> m = t.AggregateBy(x => x, (List<char>?)null, (s, x) => { (s = s ?? new List<char>()).Add(x); return s; })!;

    // a: aaa, b: bb, c: c
    Console.WriteLine(string.Join(", ", m.Select(x => $"{x.Key}: {string.Join("", x.Value)}")));
}

■ でもよさそう

でも、新しい LINQ メソッドいいですよね。今回書いている .AggregateBy メソッドも使いこなしていきたいです。そなえよう。