1. 入门
LINQ的老方法就像食堂的饭:选个 "求和"、"最小值" 或 "平均值",吃完就走。但有时候你想来点特别的,这时候 Aggregate 就像大厨,按你自己的配方做菜。
如果和函数式编程对比,Aggregate 就是 Reduce(或者 fold):你遍历集合,一步步把它“折叠”成一个值。怎么折叠?你说了算。完全自由,随便发挥。
用 Aggregate 很适合解决这些问题:
- 搞复杂的求和:比如所有数字的乘积,只加偶数/奇数,或者按特殊规则加。
- 字符串拼接,自己定逻辑(比如偶数和奇数下标用不同分隔符)。
- 生成字符串报告(比如Markdown列表、HTML、各种自定义格式)。
- 构建带状态变化的集合(比如按特殊规则把对象列表变成字典)。
- 任何标准聚合函数搞不定的复杂统计。
2. Aggregate 方法的签名和原理
来看看微软官方文档里 Enumerable.Aggregate 的签名:
public static TAccumulate Aggregate<TSource, TAccumulate>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func
)
这里每个参数是啥意思:
- source —— 你的原始集合。
- seed —— “初始值”(可以理解为起始累加器)。
- func —— 接收两个参数的函数:累积值(acc)、当前元素,返回新的累积值。
还有个简单点的重载:
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的重载才有。就像“最后来个甜点”一样处理最终结果。
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 的累加器不仅能是数字或字符串,啥都行:字典、自定义类、结构体都可以。
比如:统计每个分数有多少学生:
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?有时候你需要特殊的聚合,比如只统计不是瓦夏的学生,或者要生成特殊的报告。
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 | |
|
任意统计 | 高 | 需要seed | |
|
拼接字符串 | 中 | 空集合="" | |
8. 实用建议、常见坑和注意事项
因为 Aggregate 太灵活了,用错了容易踩坑。最常见的错误有:
有时候程序员忘了seed(初始值),没注意如果集合是空的,不带seed的重载会直接抛异常(InvalidOperationException)。所以空集合时一定要用带seed的重载:
var sum = new int[0].Aggregate(0, (acc, n) => acc + n); // 没问题!返回0
如果你用 Aggregate 拼接字符串,很容易最后多一个分隔符(比如最后多了个逗号)。最好用 .TrimEnd(',', ' ') 去掉,或者如果只是简单拼接字符串,直接用 string.Join。
累加器如果是可变类型(比如 List 或 Dictionary),在 Aggregate 里经常用,但要注意:如果你是原地修改(in-place),每一步其实都是同一个对象的引用。并发操作或者你以为会复制时,可能会出奇怪的bug。所以纯函数式风格下,最好每步都返回新对象,不要改老的。
写给别人用的代码,别为了“炫技”乱用 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