1. 入门
如果你以前写过“经典”的LINQ分组查询,比如:
var cityCounts = students.GroupBy(s => s.City)
.Select(g => new { City = g.Key, Count = g.Count() });
你肯定发现,哪怕只是按组统计个数量,代码都挺啰嗦的。.NET 9出来后,微软团队就给开发者省事儿,直接在LINQ里加了两个超常用的方法:
- CountBy —— 按key快速统计数量。
- AggregateBy —— 万能分组聚合器(不光能计数,还能求和啥的)。
顺便说下,这俩方法其实是社区呼声很高才加的,很多“进阶”LINQ库(比如 MoreLINQ)早就有类似功能了。
2. CountBy方法:分组统计so easy
描述
CountBy 就是“分组后直接统计数量”。它返回的是“key-数量”对的集合,这在实际数据分析里超常用:比如热门城市、各分数段学生数量、某东西出现频率等等。
方法签名(简化版):
IEnumerable<(TKey Key, int Count)> CountBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
实战用法
例1:按城市统计学生数量
假设有这么个类:
class Student
{
public string Name { get; set; }
public int Grade { get; set; }
public string City { get; set; }
}
然后我们有个学生列表:
var students = new List<Student>
{
new Student { Name = "伊万", Grade = 5, City = "Neonville" },
new Student { Name = "安娜", Grade = 4, City = "Los-Santos" },
new Student { Name = "叶戈尔", Grade = 3, City = "Neonville" },
new Student { Name = "玛丽亚", Grade = 5, City = "Rosewater" },
new Student { Name = "奥列格", Grade = 4, City = "Neonville" }
};
在.NET 8及以前你得这么写:
var group = students.GroupBy(s => s.City)
.Select(g => new { City = g.Key, Count = g.Count() });
foreach (var cityInfo in group)
{
Console.WriteLine($"{cityInfo.City}: {cityInfo.Count} 个学生");
}
到了.NET 9,一句话搞定:
var cityCounts = students.CountBy(s => s.City);
foreach (var (city, count) in cityCounts)
{
Console.WriteLine($"{city}: {count} 个学生");
}
没错,就是这么简单——不用手动GroupBy,不用自己算Count(),直接来!
例2:按分数统计
院长最爱的问题——优等生和及格生各有多少?
var gradeCounts = students.CountBy(s => s.Grade);
foreach (var (grade, count) in gradeCounts)
{
Console.WriteLine($"分数 {grade}: {count} 个学生");
}
例3:统计字符串里每个字符出现次数
CountBy 不光能用在对象集合,普通东西也能用:
string word = "supercalifragilisticexpialidocious";
var charFrequencies = word.CountBy(ch => ch);
foreach (var (letter, count) in charFrequencies)
{
Console.WriteLine($"{letter} : {count}");
}
内存里的样子(示意图)
| Key | Count (用CountBy前) | Count (用CountBy后) |
|---|---|---|
|
3 | 3 |
|
1 | 1 |
|
1 | 1 |
这种写法让代码更清晰,分组统计也不容易出错。
常见坑和注意点
别忘了,CountBy 返回的是(Key, Count)元组,不是匿名类型。有时候语义上没那么“优雅”,但绝对透明。另外,Count的类型只能是int,不能自定义。
3. AggregateBy方法:万能分组聚合
描述
CountBy 只会计数,AggregateBy 就是瑞士军刀!你想按销售员统计销售额、按班级找最高分,或者任何能用累加器表达的聚合,全都能搞定。
方法签名(大幅简化):
IEnumerable<(TKey Key, TAccumulate Result)> AggregateBy<TSource, TKey, TAccumulate>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func)
- keySelector —— 按什么分组。
- seed —— 累加器初始值。
- func —— 累加逻辑。
实战用法
例1:按城市统计分数总和
不用再写那种巨长的LINQ了:
var sums = students.GroupBy(s => s.City)
.Select(g => new { City = g.Key, Sum = g.Sum(s => s.Grade) });
直接这样写:
var sums = students.AggregateBy(
s => s.City, // 按城市分组
0, // 初始总和
(acc, s) => acc + s.Grade // 累加分数
);
foreach (var (city, sum) in sums)
{
Console.WriteLine($"{city}: 分数总和 = {sum}");
}
例2:按城市找最高分
想要最大值?换个累加器就行:
var maxByCity = students.AggregateBy(
s => s.City,
int.MinValue, // 初始值设成最小
(max, s) => Math.Max(max, s.Grade)
);
foreach (var (city, maxGrade) in maxByCity)
{
Console.WriteLine($"{city}: 最高分 = {maxGrade}");
}
例3:把名字拼成字符串
更新下学生列表:
var namesByCity = students.AggregateBy(
s => s.City,
"", // 初始字符串
(acc, s) => string.IsNullOrEmpty(acc) ? s.Name : acc + ", " + s.Name
);
foreach (var (city, names) in namesByCity)
{
Console.WriteLine($"{city}: {names}");
}
内部流程示意(图解)
. {Student, Student, Student, ...}
|
| (按city分组)
v
["Neonville"] ["Rosewater"] ["Los Santos"]
| | |
| (累加) |
|___________________________|
|
v
{("Neonville", 总和), ("Rosewater", 总和), ...}
和“老方法”对比
以前这些需求都得写GroupBy + Select,里面还得嵌Sum、Aggregate或者别的骚操作。现在AggregateBy直接帮你省事,代码又短又直观。
实战:用在项目里
我们的练习项目是学生日记。现在加个按城市统计平均分的报表。
// 用AggregateBy算平均分——累加总分和人数,最后算平均
var avgByCity = students.AggregateBy(
s => s.City,
(Sum: 0, Count: 0),
(acc, s) => (acc.Sum + s.Grade, acc.Count + 1)
).Select(x => (x.Key, Average: (double)x.Result.Sum / x.Result.Count));
foreach (var (city, avg) in avgByCity)
{
Console.WriteLine($"{city}: 平均分 = {avg:F2}");
}
这里我们用元组累加总分和人数,最后用总分除以人数得到平均值。
注意事项和小坑
如果你的累加器是引用类型,注意:AggregateBy里同一组每次迭代用的是同一个引用,别去改对象本身,不然可能影响整组结果。
还有:如果分组后还要变换结果,可以直接用.Select。选个合适的seed(初始值),别用错了(比如字符串别用0)。
4. 新旧写法对比
| 需求 | 老LINQ | 新LINQ .NET 9 |
|---|---|---|
| 按城市统计学生数量 | |
|
| 按城市统计分数总和 | |
|
| 按城市找最高分 | |
|
| 统计字符串里字符出现次数 | |
|
实际工作和项目里的好处
像CountBy和AggregateBy这种方法,写统计报表、做可视化、生成数据透视表啥的都超方便。代码更短、更好读,最重要的是更靠谱:不用自己手撸分组聚合,逻辑也不容易出错。
面试Junior/Middle岗位时,知道这些LINQ新方法能说明你关注平台新特性;在生产环境里,用这些优雅的写法,团队也能更快看懂和维护你的代码。
GO TO FULL VERSION