.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
メソッドも使いこなしていきたいです。そなえよう。