更新:2007 年 11 月
分组是 LINQ 最强大的功能之一。下面的示例演示如何以各种方式对数据进行分组:
按照单个属性。
按照字符串属性的首字母。
按照计算出的数值范围。
按照布尔谓词或其他表达式。
按照复合键。
此外,最后两个查询将它们的结果投影到一个新的匿名类型中,该类型仅包含学生的名字和姓氏。有关更多信息,请参见 group 子句(C# 参考)。
示例
本主题中的所有示例都使用下列帮助器类和数据源。
C# | 复制代码 |
---|---|
public class StudentClass { #region data protected enum GradeLevel { FirstYear = 1, SecondYear, ThirdYear, FourthYear }; protected class Student { public string FirstName { get; set; } public string LastName { get; set; } public int ID { get; set; } public GradeLevel Year; public List<int> ExamScores; } protected static List<Student> students = new List<Student> { new Student {FirstName = "Terry", LastName = "Adams", ID = 120, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 99, 82, 81, 79}}, new Student {FirstName = "Fadi", LastName = "Fakhouri", ID = 116, Year = GradeLevel.ThirdYear,ExamScores = new List<int>{ 99, 86, 90, 94}}, new Student {FirstName = "Hanying", LastName = "Feng", ID = 117, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 93, 92, 80, 87}}, new Student {FirstName = "Cesar", LastName = "Garcia", ID = 114, Year = GradeLevel.FourthYear,ExamScores = new List<int>{ 97, 89, 85, 82}}, new Student {FirstName = "Debra", LastName = "Garcia", ID = 115, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 35, 72, 91, 70}}, new Student {FirstName = "Hugo", LastName = "Garcia", ID = 118, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 92, 90, 83, 78}}, new Student {FirstName = "Sven", LastName = "Mortensen", ID = 113, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 88, 94, 65, 91}}, new Student {FirstName = "Claire", LastName = "O'Donnell", ID = 112, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 75, 84, 91, 39}}, new Student {FirstName = "Svetlana", LastName = "Omelchenko", ID = 111, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 97, 92, 81, 60}}, new Student {FirstName = "Lance", LastName = "Tucker", ID = 119, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 68, 79, 88, 92}}, new Student {FirstName = "Michael", LastName = "Tucker", ID = 122, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 94, 92, 91, 91}}, new Student {FirstName = "Eugene", LastName = "Zabokritski", ID = 121, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 96, 85, 91, 60}} }; #endregion //Helper method protected static int GetPercentile(Student s) { double avg = s.ExamScores.Average(); return avg > 0 ? (int)avg / 10 : 0; } public void QueryHighScores(int exam, int score) { var highScores = from student in students where student.ExamScores[exam] > score select new {Name = student.FirstName, Score = student.ExamScores[exam]}; foreach (var item in highScores) { Console.WriteLine("{0,-15}{1}", item.Name, item.Score); } } } public class Program { public static void Main() { StudentClass sc = new StudentClass(); sc.QueryHighScores(1, 90); // Keep the console window open in debug mode Console.WriteLine("Press any key to exit"); Console.ReadKey(); } } |
下面的示例演示如何通过使用元素的单个属性作为组键对源元素进行分组。在这种情况下,该键为 string。还可以使用子字符串作为键。分组操作将对该类型使用默认的相等比较器。
C# | 复制代码 |
---|---|
private static void GroupBySingleProperty() { Console.WriteLine("Group by a single property in an object"); // queryLastNames is an IEnumerable<IGrouping<string, DataClass.Student>> // var is easier to type. var queryLastNames = from student in students group student by student.LastName into newGroup orderby newGroup.Key select newGroup; foreach (var nameGroup in queryLastNames) { Console.WriteLine("Key: {0}", nameGroup.Key); foreach (var student in nameGroup) { Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName); } } } /* Output: Group by a single property in an object Key: Feng Feng, Hanying Key: Garcia Garcia, Hugo Garcia, Cesar Garcia, Debra Key: Mortensen Mortensen, Sven Key: O'Donnell O'Donnell, Claire Key: Omelchenko Omelchenko, Svetlana Key: Tucker Tucker, Michael Tucker, Lance */ |
下面的示例演示如何通过使用除对象属性以外的某个项作为组键对源元素进行分组。
C# | 复制代码 |
---|---|
private static void GroupBySubstring() { Console.WriteLine("\r\nGroup by something other than a property of the object:"); var queryFirstLetters = from student in students group student by student.LastName[0]; foreach (var studentGroup in queryFirstLetters) { Console.WriteLine("Key: {0}", studentGroup.Key); // Nested foreach is required to access group items foreach (var student in studentGroup) { Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName); } } } /* Output: Group by first character: Key: O Omelchenko, Svetlana O'Donnell, Claire Key: G Garcia, Hugo Garcia, Cesar Garcia, Debra Key: M Mortensen, Sven Key: T Tucker, Michael Tucker, Lance Key: F Feng, Hanying */ |
下面的示例演示如何通过使用某个数值范围作为组键对源元素进行分组。然后,查询将结果投影到一个匿名类型中,该类型仅包含学生的名字和姓氏以及该学生所属的百分点范围。使用匿名类型的原因是没有必要使用完整的 Student 对象来显示结果。GetPercentile 是一个帮助器函数,它根据学生的平均得分计算百分点:
C# | 复制代码 |
---|---|
static int GetPercentile(Student s) { double avg = s.Scores.Average(); return avg > 0 ? (int)avg / 10 : 0; } |
C# | 复制代码 |
---|---|
private static void GroupByRange() { Console.WriteLine("\r\nGroup by numeric range and project into a new anonymous type:"); var queryNumericRange = from student in students let percentile = GetPercentile(student) group new { student.FirstName, student.LastName } by percentile into percentGroup orderby percentGroup.Key select percentGroup; // Nested foreach required to iterate over groups and group items. foreach (var studentGroup in queryNumericRange) { Console.WriteLine("Key: {0}", (studentGroup.Key * 10)); foreach (var item in studentGroup) { Console.WriteLine("\t{0}, {1}", item.LastName, item.FirstName); } } } /* Output: Group by numeric range and project into a new anonymous type: Key: 60 Garcia, Debra Key: 70 Omelchenko, Svetlana O'Donnell, Claire Key: 80 Garcia, Hugo Mortensen, Sven Garcia, Cesar Feng, Hanying Tucker, Lance Key: 90 Tucker, Michael */ |
下面的示例演示如何通过使用布尔比较表达式对源元素进行分组。与上述示例一样,结果被投影到一个匿名类型中,因为不需要完整的源元素。请注意,在执行查询时,该匿名类型中的属性将变成 Key 成员上的属性,并且可以通过名称进行访问。
C# | 复制代码 |
---|---|
private static void GroupByBoolean() { Console.WriteLine("\r\nGroup by a boolean into two groups with string keys"); Console.WriteLine("\"True\" and \"False\" and project into a new anonymous type:"); var queryGroupByAverages = from student in students group new { student.FirstName, student.LastName } by student.ExamScores.Average() > 75 into studentGroup select studentGroup; foreach (var studentGroup in queryGroupByAverages) { Console.WriteLine("Key: {0}", studentGroup.Key); foreach (var student in studentGroup) Console.WriteLine("\t{0} {1}", student.FirstName, student.LastName); } } /* Output: Group by a boolean into two groups with string keys "True" and "False" and project into a new anonymous type: Key: True Svetlana Omelchenko Hugo Garcia Sven Mortensen Michael Tucker Cesar Garcia Hanying Feng Lance Tucker Key: False Claire O'Donnell Debra Garcia */ |
下面的示例演示如何使用匿名类型来封装包含多个值的键。在这种情况下,第二个键值是一个布尔值,它指定该学生在第一次考试中的得分是否超过了 85 分。可以按照该键中的任何属性对多组值进行排序。
C# | 复制代码 |
---|---|
private static void GroupByCompositeKey() { var queryHighScoreGroups = from student in students group student by new { FirstLetter = student.LastName[0], Score = student.ExamScores[0] > 85 } into studentGroup orderby studentGroup.Key.FirstLetter select studentGroup; Console.WriteLine("\r\nGroup and order by a compound key:"); foreach (var scoreGroup in queryHighScoreGroups) { string s = scoreGroup.Key.Score == true ? "more than" : "less than"; Console.WriteLine("Name starts with {0} who scored {1} 85", scoreGroup.Key.FirstLetter, s); foreach (var item in scoreGroup) { Console.WriteLine("\t{0} {1}", item.FirstName, item.LastName); } } } /* Output: Group and order by a compound key: Name starts with F who scored more than 85 Hanying Feng Name starts with G who scored more than 85 Hugo Garcia Cesar Garcia Name starts with G who scored less than 85 Debra Garcia Name starts with M who scored more than 85 Sven Mortensen Name starts with O who scored more than 85 Svetlana Omelchenko Name starts with O who scored less than 85 Claire O'Donnell Name starts with T who scored more than 85 Michael Tucker Name starts with T who scored less than 85 Lance Tucker */ |
编译代码
此示例包含对如何:查询对象集合(C# 编程指南)内示例应用程序中定义的对象的引用。若要编译和运行此方法,请将它粘贴到该应用程序的 StudentClass 类中,并且在 Main 方法中添加对它的调用。
在改写此方法以适合您自己的应用程序时,请记住 LINQ 需要 .NET Framework 3.5 版,并且项目必须包含一个对 System.Core.dll 的引用和一条针对 System.Linq 的 using 指令。LINQ to SQL、LINQ to XML 和 LINQ to DataSet 类型需要其他 using 指令和引用。有关更多信息,请参见