C# GroupBy的基本使用教程
起因 今天在公司做一个需求的时候,写的是面条代码,一个方法直接从头写到尾,其中用到了GroupBy,且GroupBy的KeySelector是多个属性而不是单个属性。 但是公司最近推行Clean Code,要让代码有可读性。且作为一个有追求的程序员,肯定是不能写面条代码的,要对代码进行拆分。 重构前GroupBy大概是这样子的: var groups = data.GroupBy(m => new { m.PropertyA, m.PropertyB}) 个人对于短的Linq比较习惯于用方法而不是用关键字的那种写法。 一开始这样写是没问题的,但是重构的时候问题就来了:这个groups是什么 在C#编程中,`GroupBy` 是一个非常强大的 LINQ 操作,它允许我们将数据集按照一个或多个键进行分组,以便于处理和分析。在这个基础使用教程中,我们将探讨如何正确地使用 `GroupBy` 方法以及解决在重构过程中遇到的问题。 我们来看 `GroupBy` 的基本用法。当对数据进行分组时,通常会提供一个 `KeySelector` 函数,该函数用于从每个元素中提取分组键。在描述中提到的例子中,原始的 `GroupBy` 表达式如下: ```csharp var groups = data.GroupBy(m => new { m.PropertyA, m.PropertyB }); ``` 这里的 `KeySelector` 是一个匿名对象,包含 `PropertyA` 和 `PropertyB` 两个属性。然而,在重构代码时,为了提高可读性和维护性,我们需要明确指定分组键的类型。为此,我们创建了一个名为 `EntityKey` 的类来表示这个键: ```csharp class EntityKey { public int PropertyA { get; set; } public string PropertyB { get; set; } } ... var groups = data.GroupBy(m => new EntityKey { PropertyA = m.PropertyA, PropertyB = m.PropertyB }); ``` 然而,这样做后,`GroupBy` 的行为发生了变化,原本应该合并在一起的数据现在被分成了单独的组。原因在于,默认情况下,匿名类型间的相等比较是基于对象的引用,而不是基于属性值。这意味着即使两个匿名对象的属性值相同,它们也不会被视为相等。为了解决这个问题,我们需要确保自定义的 `EntityKey` 类重写了 `Equals` 和 `GetHashCode` 方法,以便基于属性值进行相等比较: ```csharp class EntityKey { public int PropertyA { get; set; } public string PropertyB { get; set; } public override bool Equals(object obj) { if (obj is EntityKey other) { return PropertyA == other.PropertyA && PropertyB == other.PropertyB; } return false; } public override int GetHashCode() { unchecked { int hash = 17; hash = hash * 23 + PropertyA.GetHashCode(); hash = hash * 23 + PropertyB.GetHashCode(); return hash; } } } ``` 现在,`GroupBy` 应该能够正确地根据 `PropertyA` 和 `PropertyB` 的值将数据分组。在测试代码中,我们可以创建一个 `Student` 类,然后用 `GroupBy` 分组学生列表,按照年龄和班级进行分组: ```csharp class Program { static void Main(string[] args) { var list = new List<Student> { new Student(1, "Cat", 10, "University1"), new Student(2, "Dog", 10, "University1"), new Student(3, "Pig", 10, "University2"), new Student(4, "Fish", 12, "University1") }; var groups = list.GroupBy(m => new StudentKey { Age = m.Age, Class = m.Class }); foreach (var group in groups) { Console.WriteLine($"Age:{group.Key.Age},Class:{group.Key.Class}"); foreach (var student in group) { Console.WriteLine(student); } } } } class Student { // Student class properties and constructor public override string ToString() { return $"Id={Id},Name={Name},Age={Age},Class={Class}"; } } class StudentKey : IEquatable<StudentKey> { public int Age { get; set; } public string Class { get; set; } public bool Equals(StudentKey other) { return Age == other.Age && Class == other.Class; } public override bool Equals(object obj) { return obj is StudentKey key && Equals(key); } public override int GetHashCode() { unchecked { int hash = 17; hash = hash * 23 + Age.GetHashCode(); hash = hash * 23 + Class.GetHashCode(); return hash; } } } ``` 这样,我们就能正确地看到按年龄和班级分组的学生,并能正确地遍历每个组内的学生了。 总结一下,`GroupBy` 在 C# 中是用于对数据进行分组的强大工具,通过指定 `KeySelector` 来决定分组依据。在重构代码时,如果使用自定义类型作为分组键,务必确保实现了正确的相等比较逻辑,以避免因默认的引用比较导致的错误分组。通过理解并掌握这些知识点,你可以在实际项目中更有效地利用 `GroupBy` 进行数据处理。