1. はじめに
昔ながらのLINQメソッドって、学食のご飯みたいなもんだよね:"合計"とか"最小"とか"平均"を選んで、はい次!みたいな。でも、たまには特別なものが欲しくなる。そんな時にAggregateが登場。まるでシェフが君のレシピで料理してくれる感じ。
関数型プログラミングで言うと、AggregateはReduce(またはfold)だね。コレクションを一歩ずつたたんで一つの値にしていく。どうやるかは君次第。完全にコントロールできて、クリエイティブになれるよ。
Aggregateで便利に解けるタスク例:
- 複雑な合計の計算:例えば全ての数の積、偶数/奇数だけの合計、特別なルールでの合計とか。
- 手動ロジックで文字列を連結(例えば偶数・奇数インデックスで区切り文字を変えるとか)。
- 文字列レポートの生成("Markdownリスト"、HTML、カスタムフォーマットなど)。
- 状態を変えながらコレクションを作る(例えば、特別なキーでオブジェクトリストから辞書を作るとか)。
- 標準の集約関数にない複雑なカウント。
2. Aggregateメソッドのシグネチャと動作原理
Microsoftの公式ドキュメントでEnumerable.Aggregateを見てみよう:
public static TAccumulate Aggregate<TSource, TAccumulate>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func
)
それぞれの意味はこんな感じ:
- source — 君の元のコレクション。
- seed — "初期値"(スタートのアキュムレータだと思えばOK)。
- func — 2つの引数を取る関数:蓄積値(acc)、コレクションの現在要素。新しい蓄積値を返す。
もっとシンプルなオーバーロードもあるよ:
public static TSource Aggregate<TSource>(
this IEnumerable<TSource> source,
Func<TSource, TSource, TSource> func
)
この場合、"初期値"はコレクションの最初の要素で、funcは2番目から最後まで呼ばれる。
3. Aggregateの超シンプルな使い方例
学生向けのちょっとしたクイズ:AggregateでSumやProductの代わりになるって知ってた?
int[] numbers = { 2, 3, 4 };
// 合計を計算
int sum = numbers.Aggregate((acc, val) => acc + val); // acc = 蓄積, val = 次の要素
// 積を計算
int product = numbers.Aggregate((acc, val) => acc * val);
Console.WriteLine(sum); // 9
Console.WriteLine(product); // 24
見た目はSumやMultiplyっぽいけど、自分でやってる!冗談だけど、Sumメソッドなら合計だけ、でもAggregateなら合計も"アンチ合計"も"平方根の合計"もできるよ。
4. 文字列連結にAggregateを使う
全部の文字列をカンマでつなげて一つにしよう(最後に余計なカンマは付けない):
string[] words = { "C#", "LINQ", "rocks" };
string result = words.Aggregate((acc, word) => acc + ", " + word);
// 結果: "C#, LINQ, rocks"
コレクションが空かもしれない時は、初期値(seed)が便利:
// 空文字から始める
string report = words.Aggregate(
"テクノロジー: ",
(acc, word) => acc + word + "; ",
acc => acc.TrimEnd(' ', ';') // 最後の";"を消す
);
Console.WriteLine(report); // "テクノロジー: C#; LINQ; rocks"
3つ目の引数(結果セレクタ)に注目。seed付きオーバーロードにあるやつで、まるでデザートで仕上げる感じ。
5. Aggregateをアプリで使う
前からいじってる学習アプリを思い出してみて。例えばStudentクラスがあるとする:
public class Student
{
public string Name { get; set; }
public int Grade { get; set; }
}
学生リスト:
var students = new List<Student>
{
new Student { Name = "アリサ", Grade = 5 },
new Student { Name = "ボブ", Grade = 4 },
new Student { Name = "バシャ", Grade = 3 },
new Student { Name = "マリア", Grade = 5 }
};
タスク:こんな文字列を作る
"ベスト: アリサ, マリア"
— つまりGrade == 5の人全員。
初心者がよくやるやり方:
var best = "";
foreach (var s in students)
{
if (s.Grade == 5)
best += s.Name + ", ";
}
best = best.TrimEnd(',', ' ');
Console.WriteLine("ベスト: " + best);
じゃあLINQスタイルでAggregateを使うと:
var bestStr = students
.Where(s => s.Grade == 5)
.Select(s => s.Name)
.Aggregate("ベスト: ", (acc, name) => acc + name + ", ")
.TrimEnd(',', ' ');
Console.WriteLine(bestStr);
いい感じ:コード最小、読みやすさ最大。上司がLINQ知らなくても意図は伝わる(たぶん)。
6. もっと複雑なシナリオ
実はAggregateのアキュムレータは数値や文字列だけじゃなく、辞書でも自作クラスでも何でもOK。
例えば:学生リストから各成績ごとの人数を数える場合:
var gradeCounts = students.Aggregate(
new Dictionary<int, int>(),
(dict, student) => {
if (dict.ContainsKey(student.Grade))
dict[student.Grade]++;
else
dict[student.Grade] = 1;
return dict;
}
);
// 出力用:
foreach (var pair in gradeCounts)
{
Console.WriteLine($"成績 {pair.Key}: {pair.Value} 人の学生");
}
このやり方はGroupByを手動でやってる感じ。なんでGroupByを使わないの?って思うかもだけど、たまに標準LINQメソッドにない特別な集約が必要な時があるんだ。例えばバシャ以外の学生だけ合計したいとか、レポート用のレポートを作りたい時とか。
7. Aggregateの動き方を図解(フローチャート)
配列{ 2, 4, 3 }で合計をアキュムレートする場合:
acc: 2(最初の要素)
|
v
val: 4
acc = acc + val = 2 + 4 = 6
|
v
val: 3
acc = acc + val = 6 + 3 = 9
|
v
[全要素処理済み]
|
v
結果: 9
seed付きオーバーロードの場合も同じ流れ:
seed: 0
|
v
val: 2
acc = 0 + 2 = 2
|
v
val: 4
acc = 2 + 4 = 6
|
v
val: 3
acc = 6 + 3 = 9
|
v
結果: 9
Aggregateと他の集約メソッドの比較
| メソッド | 標準の動作 | 柔軟性 | 空コレクションの場合 | 動作例 |
|---|---|---|---|---|
|
数値を合計 | 低い | 0を返す | |
|
要素数を数える | 低い | 0を返す | |
|
どんな集約もOK | 高い | seedが必要 | |
|
文字列を連結 | 中くらい | 空なら"" | |
8. 実践アドバイス・よくあるミス・大事なポイント
Aggregateは柔軟すぎて、使い方を間違えると予想外のことが起きる。よくあるミスは:
たまにプログラマーがseed(初期値)を忘れて、コレクションが空の時にseed無しオーバーロードを使うとInvalidOperationExceptionが投げられる。だから空コレクションにはseed付きオーバーロードを使おう:
var sum = new int[0].Aggregate(0, (acc, n) => acc + n); // OK! 0を返す
文字列をAggregateで連結すると、最後に余計な区切り文字(例えば",")が付きやすい。.TrimEnd(',', ' ')で消すか、単に文字列連結だけならstring.Joinを使おう。
可変型アキュムレータ(ListやDictionaryなど)をAggregateで使う場合、in-placeで変更すると全ステップで同じオブジェクト参照になる。並列処理やコピーを期待してると変なことになるので、純粋関数型っぽく毎回新しいオブジェクトを返すのがベター。
他の人が使うコードでは、"カッコよさ"のためだけにAggregateを使いすぎないで。初心者にはforeachや標準集約の方が分かりやすい。単純なタスクならSum、Count、Joinを使おう。でも"特別なテンプレでテキストを作る"ならAggregateが最強!
9. 実務・面接・フレームワークとの関係
業界では、Aggregateは変わった集計やデータのたたみ込みが必要な時によく使われる。例えば複雑なレポートや統計、グラフ、ハッシュ計算、ユニークid生成、コレクションからUIコンポーネントを作る時とか。
面接ではLINQの質問がよく出るよ:"配列の合計をどうやって取る?"、"文字列リストを一つの文字列にするには?"、"ユニーク要素の数をどう数える?"みたいな。LINQやAggregateを使った答えが期待されることも多い。ちょっとひねった質問もある:"LINQで偶数の平方の合計を計算して、その過程を説明する文字列を返せますか?"とかね。
Entity Framework、Dapper、RavenDBみたいな有名.NETライブラリやフレームワークでは、AggregateはDB側で直接使うことは少ないけど、コード側でのメモリ集約にはよく使われるよ。
GO TO FULL VERSION