CodeGym /课程 /C# SELF /LINQ新方法: CountBy 和 ...

LINQ新方法: CountBy 和 AggregateBy

C# SELF
第 32 级, 课程 2
可用

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后)
"Neonville"
3 3
"Rosewater"
1 1
"Los Santos"
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
按城市统计学生数量
GroupBy + Select + Count
students.CountBy(s => s.City)
按城市统计分数总和
GroupBy + Select + Sum
students.AggregateBy(..., 0, ...)
按城市找最高分
GroupBy + Select + Max
students.AggregateBy(..., ...)
统计字符串里字符出现次数
GroupBy + Select + Count
word.CountBy(ch => ch)

实际工作和项目里的好处

像CountBy和AggregateBy这种方法,写统计报表、做可视化、生成数据透视表啥的都超方便。代码更短、更好读,最重要的是更靠谱:不用自己手撸分组聚合,逻辑也不容易出错。

面试Junior/Middle岗位时,知道这些LINQ新方法能说明你关注平台新特性;在生产环境里,用这些优雅的写法,团队也能更快看懂和维护你的代码。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION