ZetCode

C# LINQ group by

上次修改时间:2025 年 4 月 30 日

在本文中,我们将展示如何使用 LINQ 查询在 C# 中对数据进行分组。

语言集成查询 (LINQ) 是 C# 中一种强大的领域特定语言,它可以简化跨各种来源的数据查询和操作。 无论是处理数组、列表、XML 文件还是复杂的数据库,LINQ 都提供了一种统一的语法和与编程语言的无缝集成,使开发人员可以直接在代码中编写可读、富有表现力且高效的查询。

LINQ 中的 groupbyinto 关键字允许您根据特定条件将数据组织到集合中。 这些关键字使您可以轻松地在 C# 中有效地分类和操作大型数据集。 然后可以进一步处理或查询分组数据以获得见解。

group by 操作是 LINQ 中的一个强大功能,它根据共享的特征或条件将数据聚合到组中。 每个组都由一个“键”定义,该键充当组的标识符,而组中的项共享共同的属性或满足给定的表达式。 此操作通常用于总结、排序或分析结构化数据。

使用 LINQ,可以按以下方式对数据进行分组:

group 关键字指定要分组的数据,by 标识分组标准,而 into 允许进一步处理或查询分组数据。

C# LINQ group by 键

我们可以根据某个键将数据分组到不同的类别中。

Program.cs
List<Car> cars =
[
    new ("Audi", "red", 52642),
    new ("Mercedes", "blue", 57127),
    new ("Skoda", "black", 9000),
    new ("Volvo", "red", 29000),
    new ("Bentley", "yellow", 350000),
    new ("Citroen", "white", 21000),
    new ("Hummer", "black", 41400),
    new ("Volkswagen", "white", 21600),
];

var groups = from car in cars
             group car by car.Colour;

foreach (var group in groups)
{
    Console.WriteLine(group.Key);

    foreach (var car in group)
    {
        Console.WriteLine($" {car.Name} {car.Price}");
    }
}

record Car(string Name, string Colour, int Price);

我们根据汽车的颜色将汽车分成几组。

$ dotnet run
red
  Audi 52642
  Volvo 29000
blue
  Mercedes 57127
black
  Skoda 9000
  Hummer 41400
yellow
  Bentley 350000
white
  Citroen 21000
  Volkswagen 21600

C# LINQ group by 属性

我们可以按元素的某些属性对数据进行分组。

Program.cs
List<string> words =
[
    "war", "water", "cup", "cloud", "atom", "abyss", "soup", "book",
    "moon", "nice", "sky", "forest"
];

var groups = from word in words
             group word by word.Length;

foreach (var group in groups)
{
    Console.WriteLine(group.Key);

    foreach (var e in group)
    {
        Console.WriteLine($"{e}");
    }

    Console.WriteLine("----------------");
}

该示例按单词的长度对单词进行分组。

$ dotnet run
3
war
cup
sky
----------------
5
water
cloud
abyss
----------------
4
atom
soup
book
moon
nice
----------------
6
forest
----------------

C# LINQ group by 并聚合

在下面的示例中,我们执行分组和聚合操作。

Program.cs
Revenue[] revenues =
[
    new (1, "Q1", 2340),
    new (2, "Q1", 1200),
    new (3, "Q1", 980),
    new (4, "Q2", 340),
    new (5, "Q2", 780),
    new (6, "Q3", 2010),
    new (7, "Q3", 3370),
    new (8, "Q4", 540),
];

var res = from revenue in revenues
          group revenue by revenue.Quarter
          into g
          select new { Quarter = g.Key, Total = g.Sum(e => e.Amount) };

foreach (var line in res)
{
    Console.WriteLine(line);
}

record Revenue(int Id, string Quarter, int Amount);

我们有四个季度的收入。我们按季度对收入进行分组,并对金额求和。

$ dotnet run
{ Quarter = Q1, Total = 4520 }
{ Quarter = Q2, Total = 1120 }
{ Quarter = Q3, Total = 5380 }
{ Quarter = Q4, Total = 540 }

我们可以使用 where 子句对聚合后的数据应用筛选器。

Program.cs
Revenue[] revenues =
[
    new (1, "Q1", 2340),
    new (2, "Q1", 1200),
    new (3, "Q1", 980),
    new (4, "Q2", 340),
    new (5, "Q2", 780),
    new (6, "Q3", 2010),
    new (7, "Q3", 3370),
    new (8, "Q4", 540),
];


var res = from revenue in revenues
          group revenue by revenue.Quarter
          into g
          where g.Count() == 2
          select new { Quarter = g.Key, Total = g.Sum(c => c.Amount) };


foreach (var line in res)
{
    Console.WriteLine(line);
}

record Revenue(int Id, string Quarter, int Amount);

在此示例中,我们只选择那些恰好有两条收入记录的季度。

$ dotnet run
{ Quarter = Q2, Total = 1120 }
{ Quarter = Q3, Total = 5380 }

C# LINQ group by 复合键

我们可以按复合键(由多个字段组成)对数据进行分组。

Program.cs
User[] users =
[
    new ("John", "Doe", "gardener"),
    new ("Jane", "Doe", "teacher"),
    new ("Roger", "Roe", "driver"),
    new ("Peter", "Doe", "teacher"),
    new ("Pavol", "Novak", "programmer"),
    new ("Albert", "Novak", "teacher"),
    new ("Sam", "Novak", "driver"),
    new ("Peter", "Horvath", "accountant"),
    new ("Lucia", "Horvath", "accountant"),
    new ("Michael", "Novak", "programmer"),
];

var groups = from user in users
             group user by new { user.LastName, user.Occupation };

foreach (var group in groups)
{
    Console.WriteLine(group.Key);

    foreach (var e in group)
    {
        Console.WriteLine($"{e}");
    }

    Console.WriteLine("----------------");
}

record User(string FirstName, string LastName, string Occupation);

该程序按用户的姓氏和职业安排用户组。

$ dotnet run
{ LastName = Doe, Occupation = gardener }
User { FirstName = John, LastName = Doe, Occupation = gardener }
----------------
{ LastName = Doe, Occupation = teacher }
User { FirstName = Jane, LastName = Doe, Occupation = teacher }
User { FirstName = Peter, LastName = Doe, Occupation = teacher }
----------------
{ LastName = Roe, Occupation = driver }
User { FirstName = Roger, LastName = Roe, Occupation = driver }
----------------
{ LastName = Novak, Occupation = programmer }
User { FirstName = Pavol, LastName = Novak, Occupation = programmer }
User { FirstName = Michael, LastName = Novak, Occupation = programmer }
----------------
{ LastName = Novak, Occupation = teacher }
User { FirstName = Albert, LastName = Novak, Occupation = teacher }
----------------
{ LastName = Novak, Occupation = driver }
User { FirstName = Sam, LastName = Novak, Occupation = driver }
----------------
{ LastName = Horvath, Occupation = accountant }
User { FirstName = Peter, LastName = Horvath, Occupation = accountant }
User { FirstName = Lucia, LastName = Horvath, Occupation = accountant }
----------------

C# LINQ group by 布尔表达式

在以下示例中,我们使用布尔表达式对数据进行分组。

Program.cs
Student[] students =
[
    new ("John", "Doe", 78),
    new ("Roger", "Roe", 89),
    new ("Peter", "Doe", 90),
    new ("Pavol", "Novak", 34),
    new ("Albert", "Novak", 66),
    new ("Peter", "Horvath", 89),
    new ("Lucia", "Horvath", 88),
    new ("Michael", "Novak", 99),
];

var groups = from student in students
             group student by new
             {
                 Passed = student.Score > 70,
             };

foreach (var group in groups)
{
    if (group.Key.Passed)
    {
        Console.WriteLine("passed");
    }
    else
    {
        Console.WriteLine("failed");
    }

    foreach (var e in group)
    {
        Console.WriteLine(e);
    }

    Console.WriteLine("----------------------------");
}

record Student(string FirstName, string LastName, int Score);

我们有一个学生记录数组,其中包含名字、姓氏和分数字段。 要通过考试,学生需要获得 70 分以上。 使用 LINQ 表达式,我们将学生分为两组:通过和失败。

$ dotnet run
passed
Student { FirstName = John, LastName = Doe, Score = 78 }
Student { FirstName = Roger, LastName = Roe, Score = 89 }
Student { FirstName = Peter, LastName = Doe, Score = 90 }
Student { FirstName = Peter, LastName = Horvath, Score = 89 }
Student { FirstName = Lucia, LastName = Horvath, Score = 88 }
Student { FirstName = Michael, LastName = Novak, Score = 99 }
----------------------------
failed
Student { FirstName = Pavol, LastName = Novak, Score = 34 }
Student { FirstName = Albert, LastName = Novak, Score = 66 }
----------------------------

C# LINQ group by 范围

在下一个示例中,我们使用数字范围作为组键。

Program.cs
List<Student> students =
[
    new ("John", "Doe", 78),
    new ("Roger", "Roe", 89),
    new ("Peter", "Doe", 90),
    new ("Pavol", "Novak", 34),
    new ("Albert", "Novak", 66),
    new ("Peter", "Horvath", 89),
    new ("Lucia", "Horvath", 88),
    new ("Michael", "Novak", 99),
];

var groups = from std in students
             let avg = students.Average(e => e.Score)
             group std by (std.Score / 10) into g
             orderby g.Key
             select g;

foreach (var group in groups)
{
    Console.WriteLine(group.Key * 10);

    foreach (var e in group)
    {
        Console.WriteLine(e);
    }

    Console.WriteLine("----------------------------");
}

record Student(string FirstName, string LastName, int Score);

该示例将学生分组到百分位范围内。

$ dotnet run
30
Student { FirstName = Pavol, LastName = Novak, Score = 34 }
----------------------------
60
Student { FirstName = Albert, LastName = Novak, Score = 66 }
----------------------------
70
Student { FirstName = John, LastName = Doe, Score = 78 }
----------------------------
80
Student { FirstName = Roger, LastName = Roe, Score = 89 }
Student { FirstName = Peter, LastName = Horvath, Score = 89 }
Student { FirstName = Lucia, LastName = Horvath, Score = 88 }
----------------------------
90
Student { FirstName = Peter, LastName = Doe, Score = 90 }
Student { FirstName = Michael, LastName = Novak, Score = 99 }
----------------------------

C# LINQ 词频统计

在以下示例中,我们计算文件中单词的频率。

$ wget https://raw.githubusercontent.com/janbodnar/data/main/the-king-james-bible.txt

我们从詹姆斯国王钦定本圣经中读取数据。

Program.cs
using System.Text.RegularExpressions;

var fileName = "the-king-james-bible.txt";
var text = File.ReadAllText(fileName);

var dig = new Regex(@"\d");
var matches = new Regex("[a-z-A-Z']+").Matches(text);

var words = 
    from match in matches
        let val = match.Value
        where !dig.IsMatch(val)
    select match.Value;

var topTen =
    (from word in words
        group word by word into wg
        orderby wg.Count() descending
        select new {word = wg.Key, Total = wg.Count()}
    ).Take(10);

foreach (var e in topTen)
{
    Console.WriteLine($"{e.word}: {e.Total}");
}

我们统计《英王钦定本圣经》中单词的频率。

var matches = new Regex("[a-z-A-Z']+").Matches(text);
var words = matches.Select(m => m.Value).ToList();

我们使用 Matches 方法找到所有匹配项。从匹配集合中,我们将所有单词放入一个列表中。

var words = 
from match in matches
    let val = match.Value
    where !dig.IsMatch(val)
select match.Value;

在第一个查询中,我们找到所有匹配项。 从匹配集合中,我们获取所有单词。

var topTen =
    (from word in words
        group word by word into wg
        orderby wg.Count() descending
        select new {word = wg.Key, Total = wg.Count()}
    ).Take(10);

单词按频率降序分组和排序。 我们采用前十个最常见的单词。

$ dotnet run
the 62103
and 38848
of 34478
to 13400
And 12846
that 12576
in 12331
shall 9760
he 9665
unto 8942

来源

分组查询结果

在本文中,我们在 C# LINQ 中对数据进行了分组。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 C# 教程