CodeGym /课程 /C# SELF /group by分组数据

group by分组数据

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

1. 入门

分组数据——就是把集合里的元素按某个共同特征分成“篮子”。想象一下你在按颜色分苹果:红的放一篮,绿的放一篮,黄的再放一篮。在编程里这就叫分组

为啥要分组?
实际开发里,经常要按城市分人、按类别分商品、按日期分交易啥的。这样能做各种分析:比如每个班有多少学生,哪个城市学生最多。

LINQ专门有个方法:GroupBy。还有个类似的query syntax操作符——group by。我们就用学生和班级的管理系统举例,手把手教你怎么搞。

2. method syntax里的GroupBy

基本签名和返回值

GroupBy方法看起来有点吓人:


IEnumerable<IGrouping<TKey, TElement>> GroupBy<TElement, TKey>(
    this IEnumerable<TElement> source,
    Func<TElement, TKey> keySelector
)

解释一下:

  • source——原始集合(比如学生列表)。
  • keySelector——决定分组特征的函数,比如ClassName或者City属性。

注意!
结果不是你原来对象的数组,而是特殊的分组集合(IGrouping<TKey, TElement>)。每个分组包含:

  • 分组key(Key——把元素分到一起的那个东西),
  • 属于这个分组的元素集合。

图解:

Key(分组键) 分组里的元素(对象组)
"9A" 伊万, 奥列格, 玛丽亚
"10B" 斯维特拉娜, 彼得
... ...

例子1:按班级名分组学生

假设我们有个Student类:


public class Student
{
    public string Name { get; set; }
    public string ClassName { get; set; }
    public int Grade { get; set; }
}

还有学生集合:


var students = new List<Student>
{
    new Student { Name = "伊万", ClassName = "9A", Grade = 5 },
    new Student { Name = "奥列格", ClassName = "9A", Grade = 4 },
    new Student { Name = "玛丽亚", ClassName = "10B", Grade = 5 },
    new Student { Name = "斯维特拉娜", ClassName = "10B", Grade = 3 }
};

按班级分组学生:


var studentsByClass = students.GroupBy(s => s.ClassName);

foreach (var group in studentsByClass)
{
    Console.WriteLine($"班级: {group.Key}"); // 分组key
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name}, 分数: {student.Grade}");
    }
}

发生了啥?
GroupBy方法帮我们分成了两个组:"9A"一组,"10B"一组。遍历分组时,可以输出每组的各种聚合信息。

可视化:GroupBy后集合长啥样

可以这样想:


students.GroupBy(s => s.ClassName)
        ├─ group "9A" { 伊万, 奥列格 }
        └─ group "10B" { 玛丽亚, 斯维特拉娜 }

每个“分支”就是按key分出来的学生小列表。

按分数分学生:随便啥特征都能分组

我们来按分数分组:


var studentsByGrade = students.GroupBy(s => s.Grade);

foreach (var group in studentsByGrade)
{
    Console.WriteLine($"分数: {group.Key}");
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name} 来自班级 {student.ClassName}");
    }
}

输出:


分数: 5
 - 伊万 来自班级 9A
 - 玛丽亚 来自班级 10B
分数: 4
 - 奥列格 来自班级 9A
分数: 3
 - 斯维特拉娜 来自班级 10B

3. LINQ里怎么处理分组:GroupBy后还能干嘛

分组后我们经常不只是想输出分组,还想做点聚合——比如统计每组有多少人,算平均分,或者收集名字列表。

例子2:统计每个班有多少学生


var classCounts = students
    .GroupBy(s => s.ClassName)
    .Select(g => new { Class = g.Key, Count = g.Count() });

foreach (var cc in classCounts)
{
    Console.WriteLine($"班级 {cc.Class} — {cc.Count} 个学生");
}

结果:


班级 9A — 2 个学生
班级 10B — 2 个学生
  • 先按班级分组。
  • 然后对每组用匿名对象保存key(班级名)和数量(Count()方法)。

例子3:按班级收集优秀生名字


var excellentByClass = students
    .Where(s => s.Grade == 5)
    .GroupBy(s => s.ClassName)
    .Select(g => new 
    {
        Class = g.Key,
        ExcellentStudents = g.Select(s => s.Name).ToList()
    });

foreach (var group in excellentByClass)
{
    Console.WriteLine($"班级 {group.Class} 的优秀生: {string.Join(", ", group.ExcellentStudents)}");
}

4. query syntax里的group by

如果你喜欢SQL风格,或者数据库出身,LINQ也支持类似SQL的group by ... into ...语法。

分组的基本例子


var studentsByClass =
    from s in students
    group s by s.ClassName into classGroup
    select classGroup;

foreach (var group in studentsByClass)
{
    Console.WriteLine($"班级: {group.Key}");
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name}");
    }
}

先按特征分组,然后给分组起个名字(into classGroup)——后面就能随便用了。

带聚合的投影

比如想知道每个班有多少学生:


var countsByCity =
    from s in students
    group s by s.ClassName into classGroup
    select new { Class = classGroup.Key, Count = classGroup.Count() };

foreach (var item in countsByCity)
{
    Console.WriteLine($"班级 {item.Class} — {item.Count} 个学生");
}

——语法一样,select后面可以做各种聚合、列表、平均值啥的。

5. 进阶玩法

按多个特征分组(复合key)

有时候要按多个属性组合分组,比如班级+分数:


var byClassAndGrade = students
    .GroupBy(s => new { s.ClassName, s.Grade });

foreach (var group in byClassAndGrade)
{
    Console.WriteLine($"班级: {group.Key.ClassName}, 分数: {group.Key.Grade}");
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name}");
    }
}

分组key现在是匿名类型(多个字段组合)。

分组底层怎么实现?

LINQ内部小科普

  • 你用GroupBy时,LINQ内部会建个特殊“表”:每个唯一key值就是一个分组。
  • 分组实现了IGrouping<TKey, TElement>接口。可以用Key属性拿到分组特征值。

延迟执行和性能

  • 分组不是立刻算出来的,只有你遍历(foreach)时才真正生成。所以集合很大也不用怕,没用就不占内存。
  • 如果你要多次用分组,建议.ToList().ToArray(),这样结果就“固定”下来了。

实际应用场景

分组能搞各种报表、统计和分析。举几个实际例子:

  • 电商:按用户分组订单,展示每个人的购买历史。
  • 教育平台:按老师或科目分组学生。
  • HR应用:按部门分组员工,统计人数。
  • 面试时:可能让你写个真实数据的分组+聚合。

LINQ让这些需求原型超快,能看懂和写分组是.NET开发者的加分项。

可视化流程图

下面是用GroupBy处理集合的简单流程图:


[学生集合] 
      |
[按ClassName分组]
      |
[分组 "9A"] ---> [伊万, 奥列格]
[分组 "10B"] --> [玛丽亚, 斯维特拉娜]

每个分组就是带key的小集合。

6. 常见错误和坑

有些同学刚开始会被GroupBy的返回值搞晕:

  • 用了GroupBy后,你拿到的不是List<List<Student>>这种“组”,而是IGrouping<TKey, TElement>集合。要用Key属性和遍历分组元素来操作。
  • 按复合key分组(多个字段):记得用匿名类型,或者自定义类来做结构化key。
  • 只想知道每组有多少元素——别忘了Select(g => g.Count()),不然得手动数每组元素。
  • 有时候循环嵌套容易搞混:外层遍历分组,内层遍历分组里的元素。
  • lambda或key写错了,分组结果就不对(比如key都一样就全在一组)。
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION