1. 前言
老派的 LINQ 方法就像學校餐廳的飯菜:你選 "總和"、"最小值" 或 "平均值",然後就走人。但有時候你想來點特別的,這時 Aggregate 就像主廚,照你的菜單下廚。
如果拿 functional programming 來比,Aggregate 就是 Reduce(或 fold):你走過整個集合,一步步把它折成一個值。怎麼折?你自己決定。完全掌控,超有創意空間。
用 Aggregate 很方便解決的問題:
- 算複雜的總和:像是所有數字的乘積、只加偶數/奇數、或照特殊規則加總。
- 自訂邏輯合併字串(例如偶數和奇數 index 用不同分隔符)。
- 產生字串報表("Markdown 列表"、HTML、任何自訂格式)。
- 建立有狀態變化的集合(像是根據特殊 key 規則從物件清單做出字典)。
- 任何標準聚合函式沒辦法搞定的複雜計算。
2. Aggregate 方法的簽名和運作原理
來看看 Microsoft 官方文件裡的 Enumerable.Aggregate:
public static TAccumulate Aggregate<TSource, TAccumulate>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func
)
這裡每個參數是什麼意思:
- source — 你的原始集合。
- seed — "初始值"(可以想成起始累加器)。
- func — 接收兩個參數的函式:累積值(acc)、目前集合元素,回傳新的累積值。
還有個簡單的 overload:
public static TSource Aggregate<TSource>(
this IEnumerable<TSource> source,
Func<TSource, TSource, TSource> func
)
這種寫法裡,"初始值" 就是集合的第一個元素,然後 func 會從第二個跑到最後一個。
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"
注意第三個參數 — 結果轉換函式(result selector),這是有 seed overload 才有的。就像 "甜點收尾" 一樣。
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 的累加器不一定要是數字或字串,什麼都可以:字典、你自己的 class 或 struct 都行。
例如:從學生清單算出每個分數有幾個學生:
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 overload 的流程也差不多:
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 | |
|
任何計算 | 高 | 需要 seed | |
|
合併字串 | 中 | 空集合 = "" | |
8. 實用建議、常見錯誤和重點提醒
因為 Aggregate 太靈活,用錯很容易出包。最常見的錯誤有:
有時候工程師會忘記 seed(初始值),沒注意到如果集合是空的,沒 seed 的 overload 會丟出例外(InvalidOperationException)。所以空集合時請用有 seed 的 overload:
var sum = new int[0].Aggregate(0, (acc, n) => acc + n); // OK!回傳 0
如果你用 Aggregate 累加字串,很容易多出一個分隔符(像最後多一個 ",")。最好用 .TrimEnd(',', ' ') 把它去掉——或者如果只是單純合併字串,用 string.Join 就好。
累加器如果是可變型別(像 List 或 Dictionary),在 Aggregate 裡常常會用到,但要小心:如果你 in-place 修改它,每一步其實都是同一個物件的 reference。這在做平行運算或你預期會複製時可能出現怪問題。所以純函式風格下,每一步最好回傳新物件,不要改舊的。
寫給別人用的 code,別為了 "炫技" 把 Aggregate 用得太複雜:新手會覺得它比 foreach 或標準聚合難懂。如果只是單純計算,用 Sum、Count、Join 就好。但如果你要 "照特殊模板組字串"——Aggregate 就是你的好朋友!
9. 跟實際工作、面試和框架的關聯
在業界,Aggregate 常出現在需要特殊計算或資料折疊的地方,比如產生複雜報表、統計、圖形、計算 hash、產生唯一 id,甚至根據集合自訂邏輯組 UI 元件。
面試時,LINQ 幾乎一定會被問到:"怎麼算陣列元素總和?"、"怎麼把字串清單變成一條字串?" 或 "怎麼算唯一元素數量?"——而且常常希望你用 LINQ,甚至 Aggregate。有時還會出創意題:"你能用 LINQ 算出偶數的平方和,然後回傳描述這個過程的字串嗎?"
在很多熱門 .NET 函式庫和框架裡,像 Entity Framework、Dapper、RavenDB,Aggregate 很少直接在資料庫端用,但在程式碼裡做記憶體聚合時超好用。
GO TO FULL VERSION