本章概述
LINQ(Language Integrated Query,语言集成查询)是C#中最强大和优雅的特性之一,它将查询功能直接集成到C#语言中。LINQ提供了一致的查询体验,无论数据源是对象集合、数据库、XML还是其他数据源。表达式树则是LINQ的核心基础,它将代码表示为数据结构,使得查询可以被分析、修改和转换。
学习目标
通过本章学习,你将能够: - 理解LINQ的核心概念和查询语法 - 掌握LINQ to Objects的各种操作 - 学会使用方法语法和查询语法 - 理解延迟执行和立即执行的区别 - 掌握表达式树的构建和使用 - 学习自定义LINQ扩展方法 - 了解LINQ的性能优化技巧
1. LINQ基础
1.1 LINQ概念和语法
using System;
using System.Collections.Generic;
using System.Linq;
public class LinqBasics
{
public static void DemonstrateLinqBasics()
{
Console.WriteLine("\n=== LINQ基础演示 ===");
// 示例数据
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var names = new[] { "Alice", "Bob", "Charlie", "David", "Eve" };
Console.WriteLine("\n--- 查询语法 vs 方法语法 ---");
DemonstrateQuerySyntax(numbers);
DemonstrateMethodSyntax(numbers);
Console.WriteLine("\n--- 基本查询操作 ---");
DemonstrateBasicOperations(numbers, names);
}
private static void DemonstrateQuerySyntax(int[] numbers)
{
Console.WriteLine("查询语法示例:");
// 查询语法 - 类似SQL的语法
var evenNumbers = from n in numbers
where n % 2 == 0
select n;
var squaredNumbers = from n in numbers
where n > 5
select n * n;
var numbersWithIndex = from n in numbers.Select((value, index) => new { Value = value, Index = index })
where n.Index % 2 == 0
select $"索引{n.Index}: {n.Value}";
Console.WriteLine($"偶数: [{string.Join(", ", evenNumbers)}]");
Console.WriteLine($"大于5的数的平方: [{string.Join(", ", squaredNumbers)}]");
Console.WriteLine($"偶数索引的元素: [{string.Join(", ", numbersWithIndex)}]");
}
private static void DemonstrateMethodSyntax(int[] numbers)
{
Console.WriteLine("\n方法语法示例:");
// 方法语法 - 使用扩展方法链式调用
var evenNumbers = numbers.Where(n => n % 2 == 0);
var squaredNumbers = numbers
.Where(n => n > 5)
.Select(n => n * n);
var numbersWithIndex = numbers
.Select((value, index) => new { Value = value, Index = index })
.Where(item => item.Index % 2 == 0)
.Select(item => $"索引{item.Index}: {item.Value}");
Console.WriteLine($"偶数: [{string.Join(", ", evenNumbers)}]");
Console.WriteLine($"大于5的数的平方: [{string.Join(", ", squaredNumbers)}]");
Console.WriteLine($"偶数索引的元素: [{string.Join(", ", numbersWithIndex)}]");
}
private static void DemonstrateBasicOperations(int[] numbers, string[] names)
{
Console.WriteLine("\n基本查询操作:");
// 过滤 (Where)
var evenNumbers = numbers.Where(n => n % 2 == 0);
Console.WriteLine($"偶数: [{string.Join(", ", evenNumbers)}]");
// 投影 (Select)
var lengths = names.Select(name => name.Length);
Console.WriteLine($"名字长度: [{string.Join(", ", lengths)}]");
// 排序 (OrderBy, OrderByDescending)
var sortedNames = names.OrderBy(name => name.Length).ThenBy(name => name);
Console.WriteLine($"按长度排序的名字: [{string.Join(", ", sortedNames)}]");
// 分组 (GroupBy)
var groupedByLength = names.GroupBy(name => name.Length);
Console.WriteLine("按长度分组:");
foreach (var group in groupedByLength)
{
Console.WriteLine($" 长度{group.Key}: [{string.Join(", ", group)}]");
}
// 聚合操作
Console.WriteLine($"\n聚合操作:");
Console.WriteLine($"总数: {numbers.Count()}");
Console.WriteLine($"总和: {numbers.Sum()}");
Console.WriteLine($"平均值: {numbers.Average():F2}");
Console.WriteLine($"最大值: {numbers.Max()}");
Console.WriteLine($"最小值: {numbers.Min()}");
}
}
1.2 延迟执行和立即执行
public class LinqExecution
{
public static void DemonstrateExecution()
{
Console.WriteLine("\n=== LINQ执行模式演示 ===");
Console.WriteLine("\n--- 延迟执行 ---");
DemonstrateDeferredExecution();
Console.WriteLine("\n--- 立即执行 ---");
DemonstrateImmediateExecution();
Console.WriteLine("\n--- 执行时机对比 ---");
CompareExecutionTiming();
}
private static void DemonstrateDeferredExecution()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 延迟执行 - 查询定义时不执行
var evenNumbers = numbers.Where(n =>
{
Console.WriteLine($" 检查数字: {n}");
return n % 2 == 0;
});
Console.WriteLine("查询已定义,但尚未执行");
// 修改原始数据
numbers.Add(6);
numbers.Add(7);
Console.WriteLine("\n第一次枚举:");
foreach (var num in evenNumbers)
{
Console.WriteLine($"结果: {num}");
}
Console.WriteLine("\n第二次枚举:");
foreach (var num in evenNumbers)
{
Console.WriteLine($"结果: {num}");
}
}
private static void DemonstrateImmediateExecution()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 立即执行 - 使用ToList()强制执行
var evenNumbers = numbers.Where(n =>
{
Console.WriteLine($" 检查数字: {n}");
return n % 2 == 0;
}).ToList(); // 立即执行
Console.WriteLine("查询已执行并缓存结果");
// 修改原始数据
numbers.Add(6);
numbers.Add(7);
Console.WriteLine("\n枚举缓存的结果:");
foreach (var num in evenNumbers)
{
Console.WriteLine($"结果: {num}");
}
}
private static void CompareExecutionTiming()
{
var numbers = Enumerable.Range(1, 1000000);
var sw = System.Diagnostics.Stopwatch.StartNew();
// 延迟执行 - 只是定义查询
var deferredQuery = numbers
.Where(n => n % 2 == 0)
.Select(n => n * n)
.Where(n => n > 1000);
sw.Stop();
Console.WriteLine($"定义延迟查询耗时: {sw.ElapsedMilliseconds}ms");
sw.Restart();
// 执行延迟查询
var deferredResult = deferredQuery.Take(10).ToList();
sw.Stop();
Console.WriteLine($"执行延迟查询(取前10个)耗时: {sw.ElapsedMilliseconds}ms");
sw.Restart();
// 立即执行
var immediateResult = numbers
.Where(n => n % 2 == 0)
.Select(n => n * n)
.Where(n => n > 1000)
.Take(10)
.ToList();
sw.Stop();
Console.WriteLine($"立即执行查询(取前10个)耗时: {sw.ElapsedMilliseconds}ms");
Console.WriteLine($"延迟查询结果: [{string.Join(", ", deferredResult)}]");
Console.WriteLine($"立即执行结果: [{string.Join(", ", immediateResult)}]");
}
}
2. LINQ to Objects详解
2.1 过滤和投影操作
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Major { get; set; }
public double GPA { get; set; }
public List<string> Courses { get; set; } = new List<string>();
public override string ToString()
{
return $"Student(Id={Id}, Name={Name}, Age={Age}, Major={Major}, GPA={GPA:F2})";
}
}
public class LinqFiltering
{
public static void DemonstrateFiltering()
{
Console.WriteLine("\n=== LINQ过滤和投影演示 ===");
var students = GetSampleStudents();
Console.WriteLine("\n--- Where过滤 ---");
DemonstrateWhere(students);
Console.WriteLine("\n--- Select投影 ---");
DemonstrateSelect(students);
Console.WriteLine("\n--- SelectMany扁平化 ---");
DemonstrateSelectMany(students);
Console.WriteLine("\n--- 复杂查询组合 ---");
DemonstrateComplexQueries(students);
}
private static List<Student> GetSampleStudents()
{
return new List<Student>
{
new Student { Id = 1, Name = "张三", Age = 20, Major = "计算机科学", GPA = 3.8,
Courses = new List<string> { "数据结构", "算法", "数据库" } },
new Student { Id = 2, Name = "李四", Age = 22, Major = "软件工程", GPA = 3.6,
Courses = new List<string> { "软件设计", "项目管理", "测试" } },
new Student { Id = 3, Name = "王五", Age = 19, Major = "计算机科学", GPA = 3.9,
Courses = new List<string> { "机器学习", "人工智能", "数据挖掘" } },
new Student { Id = 4, Name = "赵六", Age = 21, Major = "信息系统", GPA = 3.4,
Courses = new List<string> { "系统分析", "数据库", "网络" } },
new Student { Id = 5, Name = "钱七", Age = 23, Major = "软件工程", GPA = 3.7,
Courses = new List<string> { "架构设计", "微服务", "云计算" } }
};
}
private static void DemonstrateWhere(List<Student> students)
{
// 简单条件过滤
var youngStudents = students.Where(s => s.Age < 21);
Console.WriteLine("年龄小于21的学生:");
foreach (var student in youngStudents)
{
Console.WriteLine($" {student}");
}
// 复合条件过滤
var excellentCSStudents = students.Where(s => s.Major == "计算机科学" && s.GPA > 3.7);
Console.WriteLine("\n优秀的计算机科学学生:");
foreach (var student in excellentCSStudents)
{
Console.WriteLine($" {student}");
}
// 使用索引的过滤
var evenIndexStudents = students.Where((student, index) => index % 2 == 0);
Console.WriteLine("\n偶数索引位置的学生:");
foreach (var student in evenIndexStudents)
{
Console.WriteLine($" {student}");
}
}
private static void DemonstrateSelect(List<Student> students)
{
// 简单投影
var names = students.Select(s => s.Name);
Console.WriteLine($"学生姓名: [{string.Join(", ", names)}]");
// 计算投影
var ageInMonths = students.Select(s => new { Name = s.Name, AgeInMonths = s.Age * 12 });
Console.WriteLine("\n学生年龄(月):");
foreach (var item in ageInMonths)
{
Console.WriteLine($" {item.Name}: {item.AgeInMonths}个月");
}
// 复杂对象投影
var studentSummary = students.Select(s => new
{
s.Name,
s.Major,
Grade = s.GPA >= 3.7 ? "优秀" : s.GPA >= 3.5 ? "良好" : "一般",
CourseCount = s.Courses.Count
});
Console.WriteLine("\n学生摘要:");
foreach (var summary in studentSummary)
{
Console.WriteLine($" {summary.Name} ({summary.Major}) - {summary.Grade}, {summary.CourseCount}门课程");
}
// 带索引的投影
var indexedStudents = students.Select((student, index) => new
{
Index = index + 1,
student.Name,
student.GPA
});
Console.WriteLine("\n带序号的学生列表:");
foreach (var item in indexedStudents)
{
Console.WriteLine($" {item.Index}. {item.Name} (GPA: {item.GPA:F2})");
}
}
private static void DemonstrateSelectMany(List<Student> students)
{
// 扁平化课程列表
var allCourses = students.SelectMany(s => s.Courses);
Console.WriteLine($"所有课程: [{string.Join(", ", allCourses)}]");
// 带学生信息的课程列表
var coursesWithStudent = students.SelectMany(s => s.Courses,
(student, course) => new { Student = student.Name, Course = course });
Console.WriteLine("\n学生-课程对应关系:");
foreach (var item in coursesWithStudent)
{
Console.WriteLine($" {item.Student} -> {item.Course}");
}
// 去重的课程列表
var uniqueCourses = students.SelectMany(s => s.Courses).Distinct();
Console.WriteLine($"\n去重后的课程: [{string.Join(", ", uniqueCourses)}]");
// 复杂的SelectMany示例
var majorCourseGroups = students
.GroupBy(s => s.Major)
.SelectMany(g => g.SelectMany(s => s.Courses).Distinct(),
(group, course) => new { Major = group.Key, Course = course });
Console.WriteLine("\n专业-课程分组:");
foreach (var group in majorCourseGroups.GroupBy(x => x.Major))
{
Console.WriteLine($" {group.Key}: [{string.Join(", ", group.Select(x => x.Course))}]");
}
}
private static void DemonstrateComplexQueries(List<Student> students)
{
// 复杂查询:找出每个专业GPA最高的学生
var topStudentsByMajor = students
.GroupBy(s => s.Major)
.Select(g => g.OrderByDescending(s => s.GPA).First());
Console.WriteLine("每个专业GPA最高的学生:");
foreach (var student in topStudentsByMajor)
{
Console.WriteLine($" {student}");
}
// 复杂查询:找出选修了特定课程的学生
var studentsWithDatabase = students
.Where(s => s.Courses.Contains("数据库"))
.Select(s => new { s.Name, s.Major, s.GPA });
Console.WriteLine("\n选修数据库课程的学生:");
foreach (var student in studentsWithDatabase)
{
Console.WriteLine($" {student.Name} ({student.Major}) - GPA: {student.GPA:F2}");
}
// 复杂查询:统计每门课程的选修人数
var courseStatistics = students
.SelectMany(s => s.Courses)
.GroupBy(course => course)
.Select(g => new { Course = g.Key, Count = g.Count() })
.OrderByDescending(x => x.Count);
Console.WriteLine("\n课程选修统计:");
foreach (var stat in courseStatistics)
{
Console.WriteLine($" {stat.Course}: {stat.Count}人选修");
}
}
}
2.3 集合操作和连接
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public int Credits { get; set; }
public override string ToString()
{
return $"Course(Id={Id}, Name={Name}, Department={Department}, Credits={Credits})";
}
}
public class Enrollment
{
public int StudentId { get; set; }
public int CourseId { get; set; }
public string Grade { get; set; }
public DateTime EnrollmentDate { get; set; }
}
public class LinqSetOperations
{
public static void DemonstrateSetOperations()
{
Console.WriteLine("\n=== LINQ集合操作和连接演示 ===");
var students = GetStudents();
var courses = GetCourses();
var enrollments = GetEnrollments();
Console.WriteLine("\n--- 集合操作 ---");
DemonstrateSetOps(students);
Console.WriteLine("\n--- 连接操作 ---");
DemonstrateJoins(students, courses, enrollments);
Console.WriteLine("\n--- 复杂连接查询 ---");
DemonstrateComplexJoins(students, courses, enrollments);
}
private static List<Student> GetStudents()
{
return new List<Student>
{
new Student { Id = 1, Name = "张三", Major = "计算机科学" },
new Student { Id = 2, Name = "李四", Major = "软件工程" },
new Student { Id = 3, Name = "王五", Major = "计算机科学" },
new Student { Id = 4, Name = "赵六", Major = "信息系统" }
};
}
private static List<Course> GetCourses()
{
return new List<Course>
{
new Course { Id = 101, Name = "数据结构", Department = "计算机", Credits = 3 },
new Course { Id = 102, Name = "算法分析", Department = "计算机", Credits = 3 },
new Course { Id = 103, Name = "数据库系统", Department = "计算机", Credits = 4 },
new Course { Id = 201, Name = "软件工程", Department = "软件", Credits = 3 },
new Course { Id = 202, Name = "项目管理", Department = "软件", Credits = 2 }
};
}
private static List<Enrollment> GetEnrollments()
{
return new List<Enrollment>
{
new Enrollment { StudentId = 1, CourseId = 101, Grade = "A", EnrollmentDate = DateTime.Now.AddDays(-30) },
new Enrollment { StudentId = 1, CourseId = 102, Grade = "B+", EnrollmentDate = DateTime.Now.AddDays(-25) },
new Enrollment { StudentId = 2, CourseId = 101, Grade = "B", EnrollmentDate = DateTime.Now.AddDays(-28) },
new Enrollment { StudentId = 2, CourseId = 201, Grade = "A-", EnrollmentDate = DateTime.Now.AddDays(-20) },
new Enrollment { StudentId = 3, CourseId = 102, Grade = "A", EnrollmentDate = DateTime.Now.AddDays(-22) },
new Enrollment { StudentId = 3, CourseId = 103, Grade = "B+", EnrollmentDate = DateTime.Now.AddDays(-15) }
};
}
private static void DemonstrateSetOps(List<Student> students)
{
var csStudents = students.Where(s => s.Major == "计算机科学").Select(s => s.Name);
var seStudents = students.Where(s => s.Major == "软件工程").Select(s => s.Name);
var allMajors = students.Select(s => s.Major);
Console.WriteLine($"计算机科学学生: [{string.Join(", ", csStudents)}]");
Console.WriteLine($"软件工程学生: [{string.Join(", ", seStudents)}]");
// Union - 并集
var unionResult = csStudents.Union(seStudents);
Console.WriteLine($"\nUnion(并集): [{string.Join(", ", unionResult)}]");
// Intersect - 交集
var intersectResult = csStudents.Intersect(seStudents);
Console.WriteLine($"Intersect(交集): [{string.Join(", ", intersectResult)}]");
// Except - 差集
var exceptResult = csStudents.Except(seStudents);
Console.WriteLine($"Except(差集): [{string.Join(", ", exceptResult)}]");
// Distinct - 去重
var distinctMajors = allMajors.Distinct();
Console.WriteLine($"\nDistinct专业: [{string.Join(", ", distinctMajors)}]");
// Concat - 连接(不去重)
var concatResult = csStudents.Concat(seStudents);
Console.WriteLine($"Concat(连接): [{string.Join(", ", concatResult)}]");
// 自定义比较器的集合操作
var names1 = new[] { "张三", "李四" };
var names2 = new[] { "ZHANGSAN", "王五" };
var customUnion = names1.Union(names2, StringComparer.OrdinalIgnoreCase);
Console.WriteLine($"\n自定义比较器Union: [{string.Join(", ", customUnion)}]");
}
private static void DemonstrateJoins(List<Student> students, List<Course> courses, List<Enrollment> enrollments)
{
// Inner Join - 内连接
var innerJoin = from s in students
join e in enrollments on s.Id equals e.StudentId
join c in courses on e.CourseId equals c.Id
select new
{
StudentName = s.Name,
CourseName = c.Name,
Grade = e.Grade,
Credits = c.Credits
};
Console.WriteLine("Inner Join - 学生选课信息:");
foreach (var item in innerJoin)
{
Console.WriteLine($" {item.StudentName} - {item.CourseName} ({item.Credits}学分) - 成绩: {item.Grade}");
}
// Left Join - 左连接(使用GroupJoin)
var leftJoin = from s in students
join e in enrollments on s.Id equals e.StudentId into studentEnrollments
from se in studentEnrollments.DefaultIfEmpty()
join c in courses on se?.CourseId equals c.Id into enrollmentCourses
from ec in enrollmentCourses.DefaultIfEmpty()
select new
{
StudentName = s.Name,
CourseName = ec?.Name ?? "未选课",
Grade = se?.Grade ?? "N/A"
};
Console.WriteLine("\nLeft Join - 所有学生(包括未选课):");
foreach (var item in leftJoin)
{
Console.WriteLine($" {item.StudentName} - {item.CourseName} - 成绩: {item.Grade}");
}
// Group Join - 分组连接
var groupJoin = from s in students
join e in enrollments on s.Id equals e.StudentId into studentEnrollments
select new
{
Student = s.Name,
EnrollmentCount = studentEnrollments.Count(),
Courses = studentEnrollments.Join(courses,
enrollment => enrollment.CourseId,
course => course.Id,
(enrollment, course) => new { course.Name, enrollment.Grade })
};
Console.WriteLine("\nGroup Join - 学生选课统计:");
foreach (var item in groupJoin)
{
Console.WriteLine($" {item.Student} - 选课数: {item.EnrollmentCount}");
foreach (var course in item.Courses)
{
Console.WriteLine($" {course.Name} - {course.Grade}");
}
}
}
private static void DemonstrateComplexJoins(List<Student> students, List<Course> courses, List<Enrollment> enrollments)
{
// 复杂查询:每个学生的总学分和平均成绩
var studentStats = from s in students
join e in enrollments on s.Id equals e.StudentId into studentEnrollments
from se in studentEnrollments.DefaultIfEmpty()
join c in courses on se?.CourseId equals c.Id into enrollmentCourses
from ec in enrollmentCourses.DefaultIfEmpty()
group new { Student = s, Course = ec, Enrollment = se } by s into studentGroup
select new
{
StudentName = studentGroup.Key.Name,
Major = studentGroup.Key.Major,
TotalCredits = studentGroup.Where(x => x.Course != null).Sum(x => x.Course.Credits),
CourseCount = studentGroup.Count(x => x.Course != null),
Grades = studentGroup.Where(x => x.Enrollment != null)
.Select(x => x.Enrollment.Grade).ToList()
};
Console.WriteLine("学生统计信息:");
foreach (var stat in studentStats)
{
Console.WriteLine($" {stat.StudentName} ({stat.Major}):");
Console.WriteLine($" 总学分: {stat.TotalCredits}");
Console.WriteLine($" 选课数: {stat.CourseCount}");
Console.WriteLine($" 成绩: [{string.Join(", ", stat.Grades)}]");
}
// 复杂查询:每门课程的选课统计
var courseStats = from c in courses
join e in enrollments on c.Id equals e.CourseId into courseEnrollments
from ce in courseEnrollments.DefaultIfEmpty()
join s in students on ce?.StudentId equals s.Id into enrollmentStudents
from es in enrollmentStudents.DefaultIfEmpty()
group new { Course = c, Enrollment = ce, Student = es } by c into courseGroup
select new
{
CourseName = courseGroup.Key.Name,
Department = courseGroup.Key.Department,
Credits = courseGroup.Key.Credits,
EnrollmentCount = courseGroup.Count(x => x.Student != null),
Students = courseGroup.Where(x => x.Student != null)
.Select(x => new { x.Student.Name, x.Enrollment.Grade }).ToList()
};
Console.WriteLine("\n课程统计信息:");
foreach (var stat in courseStats)
{
Console.WriteLine($" {stat.CourseName} ({stat.Department}系, {stat.Credits}学分):");
Console.WriteLine($" 选课人数: {stat.EnrollmentCount}");
if (stat.Students.Any())
{
Console.WriteLine($" 学生成绩:");
foreach (var student in stat.Students)
{
Console.WriteLine($" {student.Name}: {student.Grade}");
}
}
}
}
}
3. 表达式树基础
3.1 表达式树概念和构建
using System.Linq.Expressions;
public class ExpressionTreeBasics
{
public static void DemonstrateExpressionTrees()
{
Console.WriteLine("\n=== 表达式树基础演示 ===");
Console.WriteLine("\n--- 基本表达式树 ---");
DemonstrateBasicExpressions();
Console.WriteLine("\n--- 复杂表达式树 ---");
DemonstrateComplexExpressions();
Console.WriteLine("\n--- 表达式树分析 ---");
DemonstrateExpressionAnalysis();
Console.WriteLine("\n--- 动态表达式构建 ---");
DemonstrateDynamicExpressions();
}
private static void DemonstrateBasicExpressions()
{
// 常量表达式
Expression<Func<int>> constantExpr = () => 42;
Console.WriteLine($"常量表达式: {constantExpr}");
Console.WriteLine($"执行结果: {constantExpr.Compile()()}");
// 参数表达式
Expression<Func<int, int>> parameterExpr = x => x * 2;
Console.WriteLine($"\n参数表达式: {parameterExpr}");
Console.WriteLine($"执行结果 f(5): {parameterExpr.Compile()(5)}");
// 二元运算表达式
Expression<Func<int, int, int>> binaryExpr = (x, y) => x + y;
Console.WriteLine($"\n二元运算表达式: {binaryExpr}");
Console.WriteLine($"执行结果 f(3, 4): {binaryExpr.Compile()(3, 4)}");
// 条件表达式
Expression<Func<int, string>> conditionalExpr = x => x > 0 ? "正数" : "非正数";
Console.WriteLine($"\n条件表达式: {conditionalExpr}");
Console.WriteLine($"执行结果 f(5): {conditionalExpr.Compile()(5)}");
Console.WriteLine($"执行结果 f(-3): {conditionalExpr.Compile()(-3)}");
// 方法调用表达式
Expression<Func<string, int>> methodCallExpr = s => s.Length;
Console.WriteLine($"\n方法调用表达式: {methodCallExpr}");
Console.WriteLine($"执行结果 f(\"Hello\"): {methodCallExpr.Compile()("Hello")}");
}
private static void DemonstrateComplexExpressions()
{
// 复杂的数学表达式
Expression<Func<double, double, double>> mathExpr = (x, y) => Math.Sqrt(x * x + y * y);
Console.WriteLine($"数学表达式: {mathExpr}");
Console.WriteLine($"执行结果 f(3, 4): {mathExpr.Compile()(3, 4):F2}");
// 字符串操作表达式
Expression<Func<string, string, bool>> stringExpr = (s1, s2) => s1.Contains(s2) && s1.Length > 5;
Console.WriteLine($"\n字符串表达式: {stringExpr}");
Console.WriteLine($"执行结果 f(\"Hello World\", \"World\"): {stringExpr.Compile()("Hello World", "World")}");
// 对象属性访问表达式
Expression<Func<Student, bool>> propertyExpr = s => s.Age > 20 && s.Major == "计算机科学";
Console.WriteLine($"\n属性访问表达式: {propertyExpr}");
var student = new Student { Name = "张三", Age = 21, Major = "计算机科学" };
Console.WriteLine($"执行结果: {propertyExpr.Compile()(student)}");
// 集合操作表达式
Expression<Func<List<int>, int>> collectionExpr = list => list.Where(x => x % 2 == 0).Sum();
Console.WriteLine($"\n集合操作表达式: {collectionExpr}");
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
Console.WriteLine($"执行结果: {collectionExpr.Compile()(numbers)}");
}
private static void DemonstrateExpressionAnalysis()
{
Expression<Func<int, int, int>> expr = (x, y) => x * x + y * y;
Console.WriteLine($"表达式: {expr}");
Console.WriteLine($"表达式类型: {expr.GetType()}");
Console.WriteLine($"Body类型: {expr.Body.GetType()}");
Console.WriteLine($"参数数量: {expr.Parameters.Count}");
// 分析表达式结构
AnalyzeExpression(expr.Body, 0);
}
private static void AnalyzeExpression(Expression expr, int depth)
{
string indent = new string(' ', depth * 2);
Console.WriteLine($"{indent}节点类型: {expr.NodeType}, 表达式类型: {expr.Type}");
switch (expr)
{
case BinaryExpression binary:
Console.WriteLine($"{indent}二元运算: {binary.NodeType}");
Console.WriteLine($"{indent}左操作数:");
AnalyzeExpression(binary.Left, depth + 1);
Console.WriteLine($"{indent}右操作数:");
AnalyzeExpression(binary.Right, depth + 1);
break;
case ParameterExpression parameter:
Console.WriteLine($"{indent}参数: {parameter.Name}");
break;
case ConstantExpression constant:
Console.WriteLine($"{indent}常量: {constant.Value}");
break;
case MethodCallExpression methodCall:
Console.WriteLine($"{indent}方法调用: {methodCall.Method.Name}");
if (methodCall.Object != null)
{
Console.WriteLine($"{indent}调用对象:");
AnalyzeExpression(methodCall.Object, depth + 1);
}
foreach (var arg in methodCall.Arguments)
{
Console.WriteLine($"{indent}参数:");
AnalyzeExpression(arg, depth + 1);
}
break;
}
}
private static void DemonstrateDynamicExpressions()
{
// 动态构建表达式:x => x * factor
var parameter = Expression.Parameter(typeof(int), "x");
var factor = Expression.Constant(3);
var multiply = Expression.Multiply(parameter, factor);
var lambda = Expression.Lambda<Func<int, int>>(multiply, parameter);
Console.WriteLine($"动态构建的表达式: {lambda}");
Console.WriteLine($"执行结果 f(5): {lambda.Compile()(5)}");
// 动态构建条件表达式:x => x > threshold ? x : 0
var threshold = Expression.Constant(10);
var condition = Expression.GreaterThan(parameter, threshold);
var zero = Expression.Constant(0);
var conditional = Expression.Condition(condition, parameter, zero);
var conditionalLambda = Expression.Lambda<Func<int, int>>(conditional, parameter);
Console.WriteLine($"\n动态条件表达式: {conditionalLambda}");
Console.WriteLine($"执行结果 f(15): {conditionalLambda.Compile()(15)}");
Console.WriteLine($"执行结果 f(5): {conditionalLambda.Compile()(5)}");
// 动态构建属性访问表达式
var studentParam = Expression.Parameter(typeof(Student), "s");
var nameProperty = Expression.Property(studentParam, "Name");
var nameLength = Expression.Property(nameProperty, "Length");
var nameLengthLambda = Expression.Lambda<Func<Student, int>>(nameLength, studentParam);
Console.WriteLine($"\n动态属性访问表达式: {nameLengthLambda}");
var testStudent = new Student { Name = "张三" };
Console.WriteLine($"执行结果: {nameLengthLambda.Compile()(testStudent)}");
}
}
3.2 表达式树的修改和转换
public class ExpressionTreeModification
{
public static void DemonstrateExpressionModification()
{
Console.WriteLine("\n=== 表达式树修改和转换演示 ===");
Console.WriteLine("\n--- 表达式树访问者模式 ---");
DemonstrateExpressionVisitor();
Console.WriteLine("\n--- 参数替换 ---");
DemonstrateParameterReplacement();
Console.WriteLine("\n--- 表达式组合 ---");
DemonstrateExpressionComposition();
}
private static void DemonstrateExpressionVisitor()
{
// 创建一个表达式树访问者来计算常量
var expr = Expression.Lambda<Func<int, int>>(
Expression.Add(
Expression.Multiply(Expression.Parameter(typeof(int), "x"), Expression.Constant(2)),
Expression.Constant(10)
),
Expression.Parameter(typeof(int), "x")
);
Console.WriteLine($"原始表达式: {expr}");
// 使用自定义访问者优化常量
var optimizer = new ConstantFoldingVisitor();
var optimized = optimizer.Visit(expr);
Console.WriteLine($"优化后表达式: {optimized}");
// 测试执行结果
var original = expr.Compile();
var optimizedFunc = ((Expression<Func<int, int>>)optimized).Compile();
Console.WriteLine($"原始结果 f(5): {original(5)}");
Console.WriteLine($"优化结果 f(5): {optimizedFunc(5)}");
}
private static void DemonstrateParameterReplacement()
{
// 原始表达式: x => x * 2 + 1
var parameter = Expression.Parameter(typeof(int), "x");
var body = Expression.Add(
Expression.Multiply(parameter, Expression.Constant(2)),
Expression.Constant(1)
);
var lambda = Expression.Lambda<Func<int, int>>(body, parameter);
Console.WriteLine($"原始表达式: {lambda}");
// 替换参数为常量值
var replacer = new ParameterReplacer(parameter, Expression.Constant(10));
var replaced = replacer.Visit(lambda.Body);
Console.WriteLine($"参数替换后: {replaced}");
// 计算结果
var result = Expression.Lambda<Func<int>>(replaced).Compile()();
Console.WriteLine($"计算结果: {result}");
}
private static void DemonstrateExpressionComposition()
{
// 组合两个表达式: f(x) = x * 2, g(x) = x + 1
// 创建 h(x) = f(g(x)) = (x + 1) * 2
Expression<Func<int, int>> f = x => x * 2;
Expression<Func<int, int>> g = x => x + 1;
Console.WriteLine($"f(x): {f}");
Console.WriteLine($"g(x): {g}");
// 手动组合表达式
var parameter = Expression.Parameter(typeof(int), "x");
var gBody = new ParameterReplacer(g.Parameters[0], parameter).Visit(g.Body);
var composedBody = new ParameterReplacer(f.Parameters[0], gBody).Visit(f.Body);
var composed = Expression.Lambda<Func<int, int>>(composedBody, parameter);
Console.WriteLine($"h(x) = f(g(x)): {composed}");
// 测试组合函数
var fFunc = f.Compile();
var gFunc = g.Compile();
var hFunc = composed.Compile();
int testValue = 5;
Console.WriteLine($"\n测试值: {testValue}");
Console.WriteLine($"g({testValue}) = {gFunc(testValue)}");
Console.WriteLine($"f(g({testValue})) = {fFunc(gFunc(testValue))}");
Console.WriteLine($"h({testValue}) = {hFunc(testValue)}");
}
}
// 常量折叠访问者
public class ConstantFoldingVisitor : ExpressionVisitor
{
protected override Expression VisitBinary(BinaryExpression node)
{
var left = Visit(node.Left);
var right = Visit(node.Right);
// 如果两个操作数都是常量,则计算结果
if (left is ConstantExpression leftConst && right is ConstantExpression rightConst)
{
try
{
var leftValue = leftConst.Value;
var rightValue = rightConst.Value;
object result = node.NodeType switch
{
ExpressionType.Add => (int)leftValue + (int)rightValue,
ExpressionType.Subtract => (int)leftValue - (int)rightValue,
ExpressionType.Multiply => (int)leftValue * (int)rightValue,
ExpressionType.Divide => (int)leftValue / (int)rightValue,
_ => null
};
if (result != null)
{
return Expression.Constant(result, node.Type);
}
}
catch
{
// 如果计算失败,返回原始表达式
}
}
return Expression.MakeBinary(node.NodeType, left, right);
}
}
// 参数替换访问者
public class ParameterReplacer : ExpressionVisitor
{
private readonly ParameterExpression _parameter;
private readonly Expression _replacement;
public ParameterReplacer(ParameterExpression parameter, Expression replacement)
{
_parameter = parameter;
_replacement = replacement;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node == _parameter ? _replacement : node;
}
}
4. 自定义LINQ扩展方法
4.1 基础扩展方法
public static class CustomLinqExtensions
{
// 批处理扩展方法
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int batchSize)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (batchSize <= 0) throw new ArgumentException("Batch size must be positive", nameof(batchSize));
return BatchImpl(source, batchSize);
}
private static IEnumerable<IEnumerable<T>> BatchImpl<T>(IEnumerable<T> source, int batchSize)
{
var batch = new List<T>(batchSize);
foreach (var item in source)
{
batch.Add(item);
if (batch.Count == batchSize)
{
yield return batch;
batch = new List<T>(batchSize);
}
}
if (batch.Count > 0)
{
yield return batch;
}
}
// 窗口函数扩展方法
public static IEnumerable<IEnumerable<T>> Window<T>(this IEnumerable<T> source, int windowSize)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (windowSize <= 0) throw new ArgumentException("Window size must be positive", nameof(windowSize));
return WindowImpl(source, windowSize);
}
private static IEnumerable<IEnumerable<T>> WindowImpl<T>(IEnumerable<T> source, int windowSize)
{
var window = new Queue<T>(windowSize);
foreach (var item in source)
{
window.Enqueue(item);
if (window.Count > windowSize)
{
window.Dequeue();
}
if (window.Count == windowSize)
{
yield return window.ToArray();
}
}
}
// 条件聚合扩展方法
public static TResult AggregateWhile<T, TResult>(
this IEnumerable<T> source,
TResult seed,
Func<TResult, T, TResult> accumulator,
Func<TResult, T, bool> condition)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (accumulator == null) throw new ArgumentNullException(nameof(accumulator));
if (condition == null) throw new ArgumentNullException(nameof(condition));
var result = seed;
foreach (var item in source)
{
if (!condition(result, item))
break;
result = accumulator(result, item);
}
return result;
}
// 安全的FirstOrDefault扩展
public static T FirstOrDefault<T>(this IEnumerable<T> source, T defaultValue)
{
if (source == null) throw new ArgumentNullException(nameof(source));
foreach (var item in source)
{
return item;
}
return defaultValue;
}
// 带索引的Where扩展
public static IEnumerable<T> WhereWithIndex<T>(this IEnumerable<T> source, Func<T, int, bool> predicate)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
int index = 0;
foreach (var item in source)
{
if (predicate(item, index))
{
yield return item;
}
index++;
}
}
// 递归扩展方法
public static IEnumerable<T> Traverse<T>(this T root, Func<T, IEnumerable<T>> getChildren)
{
if (root == null) yield break;
if (getChildren == null) throw new ArgumentNullException(nameof(getChildren));
var stack = new Stack<T>();
stack.Push(root);
while (stack.Count > 0)
{
var current = stack.Pop();
yield return current;
var children = getChildren(current);
if (children != null)
{
foreach (var child in children.Reverse())
{
stack.Push(child);
}
}
}
}
// 去重扩展方法(基于键)
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
var seen = new HashSet<TKey>();
foreach (var item in source)
{
var key = keySelector(item);
if (seen.Add(key))
{
yield return item;
}
}
}
}
// 扩展方法使用示例
public class CustomLinqDemo
{
public static void DemonstrateCustomExtensions()
{
Console.WriteLine("\n=== 自定义LINQ扩展方法演示 ===");
var numbers = Enumerable.Range(1, 20).ToList();
Console.WriteLine($"原始数据: [{string.Join(", ", numbers)}]");
Console.WriteLine("\n--- Batch扩展方法 ---");
var batches = numbers.Batch(5);
foreach (var batch in batches)
{
Console.WriteLine($"批次: [{string.Join(", ", batch)}]");
}
Console.WriteLine("\n--- Window扩展方法 ---");
var windows = numbers.Take(10).Window(3);
foreach (var window in windows)
{
Console.WriteLine($"窗口: [{string.Join(", ", window)}]");
}
Console.WriteLine("\n--- AggregateWhile扩展方法 ---");
var sumWhileLessThan50 = numbers.AggregateWhile(
0,
(acc, x) => acc + x,
(acc, x) => acc + x <= 50
);
Console.WriteLine($"累加直到超过50: {sumWhileLessThan50}");
Console.WriteLine("\n--- WhereWithIndex扩展方法 ---");
var evenIndexItems = numbers.Take(10).WhereWithIndex((item, index) => index % 2 == 0);
Console.WriteLine($"偶数索引项: [{string.Join(", ", evenIndexItems)}]");
Console.WriteLine("\n--- DistinctBy扩展方法 ---");
var students = new[]
{
new { Name = "张三", Age = 20 },
new { Name = "李四", Age = 21 },
new { Name = "王五", Age = 20 },
new { Name = "赵六", Age = 22 }
};
var distinctByAge = students.DistinctBy(s => s.Age);
Console.WriteLine("按年龄去重:");
foreach (var student in distinctByAge)
{
Console.WriteLine($" {student.Name} - {student.Age}岁");
}
Console.WriteLine("\n--- Traverse扩展方法 ---");
var tree = new TreeNode
{
Value = "Root",
Children = new[]
{
new TreeNode
{
Value = "Child1",
Children = new[]
{
new TreeNode { Value = "Grandchild1" },
new TreeNode { Value = "Grandchild2" }
}
},
new TreeNode { Value = "Child2" }
}
};
var allNodes = tree.Traverse(node => node.Children ?? Enumerable.Empty<TreeNode>());
Console.WriteLine($"遍历树节点: [{string.Join(", ", allNodes.Select(n => n.Value))}]");
}
}
public class TreeNode
{
public string Value { get; set; }
public IEnumerable<TreeNode> Children { get; set; }
}
5. LINQ性能优化
5.1 性能分析和优化策略
using System.Diagnostics;
public class LinqPerformanceOptimization
{
public static void DemonstratePerformanceOptimization()
{
Console.WriteLine("\n=== LINQ性能优化演示 ===");
Console.WriteLine("\n--- 延迟执行 vs 立即执行 ---");
DemonstrateDeferredVsImmediate();
Console.WriteLine("\n--- 查询优化 ---");
DemonstrateQueryOptimization();
Console.WriteLine("\n--- 内存使用优化 ---");
DemonstrateMemoryOptimization();
Console.WriteLine("\n--- 并行LINQ性能 ---");
DemonstrateParallelLinqPerformance();
}
private static void DemonstrateDeferredVsImmediate()
{
var data = Enumerable.Range(1, 1000000).ToList();
// 延迟执行
var sw = Stopwatch.StartNew();
var deferredQuery = data.Where(x => x % 2 == 0).Select(x => x * x);
sw.Stop();
Console.WriteLine($"延迟查询创建时间: {sw.ElapsedMilliseconds}ms");
// 立即执行
sw.Restart();
var immediateQuery = data.Where(x => x % 2 == 0).Select(x => x * x).ToList();
sw.Stop();
Console.WriteLine($"立即执行时间: {sw.ElapsedMilliseconds}ms");
// 延迟执行的实际执行
sw.Restart();
var deferredResult = deferredQuery.ToList();
sw.Stop();
Console.WriteLine($"延迟查询执行时间: {sw.ElapsedMilliseconds}ms");
Console.WriteLine($"结果数量: {immediateQuery.Count} vs {deferredResult.Count}");
}
private static void DemonstrateQueryOptimization()
{
var data = Enumerable.Range(1, 100000)
.Select(i => new { Id = i, Value = i * 2, Category = i % 10 })
.ToList();
// 低效查询:多次遍历
var sw = Stopwatch.StartNew();
var inefficientResult = data
.Where(x => x.Category == 5)
.Where(x => x.Value > 1000)
.Select(x => x.Id)
.ToList();
sw.Stop();
var inefficientTime = sw.ElapsedMilliseconds;
// 高效查询:单次遍历
sw.Restart();
var efficientResult = data
.Where(x => x.Category == 5 && x.Value > 1000)
.Select(x => x.Id)
.ToList();
sw.Stop();
var efficientTime = sw.ElapsedMilliseconds;
Console.WriteLine($"低效查询时间: {inefficientTime}ms");
Console.WriteLine($"高效查询时间: {efficientTime}ms");
Console.WriteLine($"性能提升: {(double)inefficientTime / efficientTime:F2}x");
Console.WriteLine($"结果数量: {inefficientResult.Count} vs {efficientResult.Count}");
// 使用索引优化
var indexedData = data.ToLookup(x => x.Category);
sw.Restart();
var indexedResult = indexedData[5]
.Where(x => x.Value > 1000)
.Select(x => x.Id)
.ToList();
sw.Stop();
var indexedTime = sw.ElapsedMilliseconds;
Console.WriteLine($"索引查询时间: {indexedTime}ms");
Console.WriteLine($"索引性能提升: {(double)efficientTime / Math.Max(indexedTime, 1):F2}x");
}
private static void DemonstrateMemoryOptimization()
{
// 内存友好的大数据处理
Console.WriteLine("\n内存使用对比:");
// 方法1:全部加载到内存
var sw = Stopwatch.StartNew();
var memoryIntensive = Enumerable.Range(1, 1000000)
.Where(x => x % 1000 == 0)
.Select(x => x.ToString())
.ToList(); // 立即执行,占用内存
sw.Stop();
Console.WriteLine($"内存密集方法时间: {sw.ElapsedMilliseconds}ms");
Console.WriteLine($"结果数量: {memoryIntensive.Count}");
// 方法2:流式处理
sw.Restart();
var streamingCount = 0;
foreach (var item in Enumerable.Range(1, 1000000)
.Where(x => x % 1000 == 0)
.Select(x => x.ToString()))
{
streamingCount++;
// 处理单个项目,不占用大量内存
}
sw.Stop();
Console.WriteLine($"流式处理时间: {sw.ElapsedMilliseconds}ms");
Console.WriteLine($"处理数量: {streamingCount}");
// 使用yield return的自定义方法
sw.Restart();
var yieldCount = ProcessLargeDataset().Count();
sw.Stop();
Console.WriteLine($"Yield方法时间: {sw.ElapsedMilliseconds}ms");
Console.WriteLine($"处理数量: {yieldCount}");
}
private static IEnumerable<string> ProcessLargeDataset()
{
for (int i = 1; i <= 1000000; i++)
{
if (i % 1000 == 0)
{
yield return i.ToString();
}
}
}
private static void DemonstrateParallelLinqPerformance()
{
var data = Enumerable.Range(1, 1000000).ToList();
// 串行处理
var sw = Stopwatch.StartNew();
var sequentialResult = data
.Where(x => IsPrime(x))
.Sum();
sw.Stop();
var sequentialTime = sw.ElapsedMilliseconds;
// 并行处理
sw.Restart();
var parallelResult = data
.AsParallel()
.Where(x => IsPrime(x))
.Sum();
sw.Stop();
var parallelTime = sw.ElapsedMilliseconds;
Console.WriteLine($"串行处理时间: {sequentialTime}ms");
Console.WriteLine($"并行处理时间: {parallelTime}ms");
Console.WriteLine($"并行性能提升: {(double)sequentialTime / parallelTime:F2}x");
Console.WriteLine($"结果: {sequentialResult} vs {parallelResult}");
// 配置并行度
sw.Restart();
var configuredParallelResult = data
.AsParallel()
.WithDegreeOfParallelism(Environment.ProcessorCount / 2)
.Where(x => IsPrime(x))
.Sum();
sw.Stop();
var configuredTime = sw.ElapsedMilliseconds;
Console.WriteLine($"配置并行度时间: {configuredTime}ms");
}
private static bool IsPrime(int number)
{
if (number < 2) return false;
if (number == 2) return true;
if (number % 2 == 0) return false;
for (int i = 3; i * i <= number; i += 2)
{
if (number % i == 0) return false;
}
return true;
}
}
5.2 最佳实践和常见陷阱
public class LinqBestPractices
{
public static void DemonstrateBestPractices()
{
Console.WriteLine("\n=== LINQ最佳实践演示 ===");
Console.WriteLine("\n--- 避免多次枚举 ---");
AvoidMultipleEnumeration();
Console.WriteLine("\n--- 正确使用Any和Count ---");
UseAnyAndCountCorrectly();
Console.WriteLine("\n--- 选择合适的集合类型 ---");
ChooseRightCollectionType();
Console.WriteLine("\n--- 异常处理最佳实践 ---");
HandleExceptionsCorrectly();
}
private static void AvoidMultipleEnumeration()
{
// 错误做法:多次枚举
var expensiveQuery = GetExpensiveData().Where(x => x > 100);
Console.WriteLine("错误做法 - 多次枚举:");
var sw = Stopwatch.StartNew();
if (expensiveQuery.Any())
{
Console.WriteLine($"找到 {expensiveQuery.Count()} 个结果");
Console.WriteLine($"第一个结果: {expensiveQuery.First()}");
}
sw.Stop();
Console.WriteLine($"多次枚举时间: {sw.ElapsedMilliseconds}ms");
// 正确做法:缓存结果
sw.Restart();
var cachedResults = GetExpensiveData().Where(x => x > 100).ToList();
if (cachedResults.Any())
{
Console.WriteLine($"\n正确做法 - 缓存结果:");
Console.WriteLine($"找到 {cachedResults.Count} 个结果");
Console.WriteLine($"第一个结果: {cachedResults.First()}");
}
sw.Stop();
Console.WriteLine($"缓存结果时间: {sw.ElapsedMilliseconds}ms");
}
private static IEnumerable<int> GetExpensiveData()
{
Console.WriteLine(" 执行昂贵的数据获取操作...");
Thread.Sleep(100); // 模拟昂贵操作
return Enumerable.Range(1, 1000);
}
private static void UseAnyAndCountCorrectly()
{
var data = Enumerable.Range(1, 1000000).ToList();
// 检查是否存在元素
var sw = Stopwatch.StartNew();
bool hasEvenNumbers1 = data.Where(x => x % 2 == 0).Count() > 0; // 错误
sw.Stop();
var wrongTime = sw.ElapsedMilliseconds;
sw.Restart();
bool hasEvenNumbers2 = data.Any(x => x % 2 == 0); // 正确
sw.Stop();
var rightTime = sw.ElapsedMilliseconds;
Console.WriteLine($"使用Count() > 0: {wrongTime}ms");
Console.WriteLine($"使用Any(): {rightTime}ms");
Console.WriteLine($"性能提升: {(double)wrongTime / Math.Max(rightTime, 1):F2}x");
// 检查特定数量
sw.Restart();
bool hasManyItems1 = data.Where(x => x % 100 == 0).Count() >= 5; // 可以接受
sw.Stop();
var countTime = sw.ElapsedMilliseconds;
sw.Restart();
bool hasManyItems2 = data.Where(x => x % 100 == 0).Skip(4).Any(); // 更高效
sw.Stop();
var skipAnyTime = sw.ElapsedMilliseconds;
Console.WriteLine($"\n使用Count() >= 5: {countTime}ms");
Console.WriteLine($"使用Skip(4).Any(): {skipAnyTime}ms");
}
private static void ChooseRightCollectionType()
{
var data = Enumerable.Range(1, 100000).ToList();
// 频繁查找 - 使用HashSet
var sw = Stopwatch.StartNew();
var listLookup = data.ToList();
bool found1 = listLookup.Contains(99999);
sw.Stop();
var listTime = sw.ElapsedMilliseconds;
sw.Restart();
var hashSetLookup = data.ToHashSet();
bool found2 = hashSetLookup.Contains(99999);
sw.Stop();
var hashSetTime = sw.ElapsedMilliseconds;
Console.WriteLine($"List查找时间: {listTime}ms");
Console.WriteLine($"HashSet查找时间: {hashSetTime}ms");
// 分组查找 - 使用Lookup
sw.Restart();
var groupedList = data.GroupBy(x => x % 10).ToList();
var group1 = groupedList.First(g => g.Key == 5).ToList();
sw.Stop();
var groupByTime = sw.ElapsedMilliseconds;
sw.Restart();
var lookup = data.ToLookup(x => x % 10);
var group2 = lookup[5].ToList();
sw.Stop();
var lookupTime = sw.ElapsedMilliseconds;
Console.WriteLine($"\nGroupBy查找时间: {groupByTime}ms");
Console.WriteLine($"Lookup查找时间: {lookupTime}ms");
}
private static void HandleExceptionsCorrectly()
{
var data = new[] { "1", "2", "abc", "4", "5" };
// 错误做法:异常会中断整个查询
try
{
var wrongResult = data.Select(int.Parse).Sum();
Console.WriteLine($"错误做法结果: {wrongResult}");
}
catch (Exception ex)
{
Console.WriteLine($"错误做法异常: {ex.Message}");
}
// 正确做法1:使用TryParse
var correctResult1 = data
.Select(s => int.TryParse(s, out int value) ? value : (int?)null)
.Where(x => x.HasValue)
.Sum(x => x.Value);
Console.WriteLine($"正确做法1结果: {correctResult1}");
// 正确做法2:使用Where过滤
var correctResult2 = data
.Where(s => int.TryParse(s, out _))
.Select(int.Parse)
.Sum();
Console.WriteLine($"正确做法2结果: {correctResult2}");
// 正确做法3:使用自定义扩展方法
var correctResult3 = data.TrySelect(int.Parse).Sum();
Console.WriteLine($"正确做法3结果: {correctResult3}");
}
}
// 安全转换扩展方法
public static class SafeLinqExtensions
{
public static IEnumerable<TResult> TrySelect<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, TResult> selector)
{
foreach (var item in source)
{
TResult result;
try
{
result = selector(item);
yield return result;
}
catch
{
// 忽略转换失败的项目
continue;
}
}
}
}
6. 实践练习
6.1 数据分析系统
public class SalesRecord
{
public int Id { get; set; }
public DateTime Date { get; set; }
public string Product { get; set; }
public string Category { get; set; }
public decimal Amount { get; set; }
public string Region { get; set; }
public string SalesPerson { get; set; }
}
public class DataAnalysisSystem
{
private readonly List<SalesRecord> _salesData;
public DataAnalysisSystem()
{
_salesData = GenerateSampleData();
}
public void RunAnalysis()
{
Console.WriteLine("\n=== 数据分析系统演示 ===");
Console.WriteLine("\n--- 基础统计 ---");
BasicStatistics();
Console.WriteLine("\n--- 时间序列分析 ---");
TimeSeriesAnalysis();
Console.WriteLine("\n--- 地区分析 ---");
RegionalAnalysis();
Console.WriteLine("\n--- 销售人员绩效 ---");
SalesPersonPerformance();
Console.WriteLine("\n--- 产品分析 ---");
ProductAnalysis();
}
private void BasicStatistics()
{
var totalSales = _salesData.Sum(s => s.Amount);
var averageSale = _salesData.Average(s => s.Amount);
var maxSale = _salesData.Max(s => s.Amount);
var minSale = _salesData.Min(s => s.Amount);
var totalRecords = _salesData.Count;
Console.WriteLine($"总销售额: {totalSales:C}");
Console.WriteLine($"平均销售额: {averageSale:C}");
Console.WriteLine($"最大单笔销售: {maxSale:C}");
Console.WriteLine($"最小单笔销售: {minSale:C}");
Console.WriteLine($"总记录数: {totalRecords}");
// 按类别统计
var categoryStats = _salesData
.GroupBy(s => s.Category)
.Select(g => new
{
Category = g.Key,
TotalSales = g.Sum(s => s.Amount),
Count = g.Count(),
AverageSale = g.Average(s => s.Amount)
})
.OrderByDescending(x => x.TotalSales);
Console.WriteLine("\n按类别统计:");
foreach (var stat in categoryStats)
{
Console.WriteLine($" {stat.Category}: {stat.TotalSales:C} ({stat.Count}笔, 平均{stat.AverageSale:C})");
}
}
private void TimeSeriesAnalysis()
{
// 按月统计
var monthlySales = _salesData
.GroupBy(s => new { s.Date.Year, s.Date.Month })
.Select(g => new
{
Year = g.Key.Year,
Month = g.Key.Month,
TotalSales = g.Sum(s => s.Amount),
Count = g.Count()
})
.OrderBy(x => x.Year).ThenBy(x => x.Month);
Console.WriteLine("月度销售趋势:");
foreach (var month in monthlySales)
{
Console.WriteLine($" {month.Year}-{month.Month:D2}: {month.TotalSales:C} ({month.Count}笔)");
}
// 计算增长率
var monthlyGrowth = monthlySales
.Zip(monthlySales.Skip(1), (prev, curr) => new
{
Month = $"{curr.Year}-{curr.Month:D2}",
GrowthRate = (curr.TotalSales - prev.TotalSales) / prev.TotalSales * 100
});
Console.WriteLine("\n月度增长率:");
foreach (var growth in monthlyGrowth)
{
Console.WriteLine($" {growth.Month}: {growth.GrowthRate:F2}%");
}
}
private void RegionalAnalysis()
{
var regionalStats = _salesData
.GroupBy(s => s.Region)
.Select(g => new
{
Region = g.Key,
TotalSales = g.Sum(s => s.Amount),
AverageSale = g.Average(s => s.Amount),
TopProduct = g.GroupBy(s => s.Product)
.OrderByDescending(pg => pg.Sum(s => s.Amount))
.First().Key,
SalesPersonCount = g.Select(s => s.SalesPerson).Distinct().Count()
})
.OrderByDescending(x => x.TotalSales);
Console.WriteLine("地区销售分析:");
foreach (var region in regionalStats)
{
Console.WriteLine($" {region.Region}:");
Console.WriteLine($" 总销售额: {region.TotalSales:C}");
Console.WriteLine($" 平均销售额: {region.AverageSale:C}");
Console.WriteLine($" 热销产品: {region.TopProduct}");
Console.WriteLine($" 销售人员数: {region.SalesPersonCount}");
}
}
private void SalesPersonPerformance()
{
var performanceStats = _salesData
.GroupBy(s => s.SalesPerson)
.Select(g => new
{
SalesPerson = g.Key,
TotalSales = g.Sum(s => s.Amount),
SalesCount = g.Count(),
AverageSale = g.Average(s => s.Amount),
BestMonth = g.GroupBy(s => new { s.Date.Year, s.Date.Month })
.OrderByDescending(mg => mg.Sum(s => s.Amount))
.First(),
ProductDiversity = g.Select(s => s.Product).Distinct().Count()
})
.OrderByDescending(x => x.TotalSales)
.Take(10);
Console.WriteLine("销售人员绩效 (前10名):");
foreach (var perf in performanceStats)
{
Console.WriteLine($" {perf.SalesPerson}:");
Console.WriteLine($" 总销售额: {perf.TotalSales:C}");
Console.WriteLine($" 销售笔数: {perf.SalesCount}");
Console.WriteLine($" 平均单笔: {perf.AverageSale:C}");
Console.WriteLine($" 最佳月份: {perf.BestMonth.Key.Year}-{perf.BestMonth.Key.Month:D2} ({perf.BestMonth.Sum(s => s.Amount):C})");
Console.WriteLine($" 产品多样性: {perf.ProductDiversity}种产品");
}
}
private void ProductAnalysis()
{
// 产品生命周期分析
var productLifecycle = _salesData
.GroupBy(s => s.Product)
.Select(g => new
{
Product = g.Key,
FirstSale = g.Min(s => s.Date),
LastSale = g.Max(s => s.Date),
TotalSales = g.Sum(s => s.Amount),
SalesCount = g.Count(),
PeakMonth = g.GroupBy(s => new { s.Date.Year, s.Date.Month })
.OrderByDescending(mg => mg.Sum(s => s.Amount))
.First()
})
.OrderByDescending(x => x.TotalSales)
.Take(5);
Console.WriteLine("产品生命周期分析 (前5名):");
foreach (var product in productLifecycle)
{
var lifecycle = (product.LastSale - product.FirstSale).Days;
Console.WriteLine($" {product.Product}:");
Console.WriteLine($" 总销售额: {product.TotalSales:C}");
Console.WriteLine($" 生命周期: {lifecycle}天");
Console.WriteLine($" 首次销售: {product.FirstSale:yyyy-MM-dd}");
Console.WriteLine($" 最后销售: {product.LastSale:yyyy-MM-dd}");
Console.WriteLine($" 销售高峰: {product.PeakMonth.Key.Year}-{product.PeakMonth.Key.Month:D2} ({product.PeakMonth.Sum(s => s.Amount):C})");
}
// 产品关联分析
var productPairs = _salesData
.GroupBy(s => new { s.SalesPerson, Date = s.Date.Date })
.Where(g => g.Select(s => s.Product).Distinct().Count() > 1)
.SelectMany(g =>
from p1 in g.Select(s => s.Product).Distinct()
from p2 in g.Select(s => s.Product).Distinct()
where string.Compare(p1, p2) < 0
select new { Product1 = p1, Product2 = p2 })
.GroupBy(pair => new { pair.Product1, pair.Product2 })
.Select(g => new
{
Product1 = g.Key.Product1,
Product2 = g.Key.Product2,
CoOccurrence = g.Count()
})
.OrderByDescending(x => x.CoOccurrence)
.Take(5);
Console.WriteLine("\n产品关联分析 (经常一起销售的产品):");
foreach (var pair in productPairs)
{
Console.WriteLine($" {pair.Product1} + {pair.Product2}: {pair.CoOccurrence}次");
}
}
private List<SalesRecord> GenerateSampleData()
{
var random = new Random(42);
var products = new[] { "笔记本电脑", "台式机", "显示器", "键盘", "鼠标", "耳机", "音响", "摄像头" };
var categories = new[] { "电脑", "配件", "外设" };
var regions = new[] { "华北", "华东", "华南", "西南", "东北" };
var salesPeople = new[] { "张三", "李四", "王五", "赵六", "钱七", "孙八", "周九", "吴十" };
var data = new List<SalesRecord>();
var startDate = DateTime.Now.AddMonths(-12);
for (int i = 0; i < 10000; i++)
{
var product = products[random.Next(products.Length)];
var category = product switch
{
"笔记本电脑" or "台式机" => "电脑",
"显示器" => "配件",
_ => "外设"
};
data.Add(new SalesRecord
{
Id = i + 1,
Date = startDate.AddDays(random.Next(365)),
Product = product,
Category = category,
Amount = random.Next(100, 5000),
Region = regions[random.Next(regions.Length)],
SalesPerson = salesPeople[random.Next(salesPeople.Length)]
});
}
return data;
}
}
6.2 动态查询构建器
public class DynamicQueryBuilder<T>
{
private IQueryable<T> _query;
private readonly List<Expression<Func<T, bool>>> _filters;
private readonly List<(Expression<Func<T, object>> KeySelector, bool Descending)> _orderBy;
public DynamicQueryBuilder(IQueryable<T> source)
{
_query = source;
_filters = new List<Expression<Func<T, bool>>>();
_orderBy = new List<(Expression<Func<T, object>>, bool)>();
}
public DynamicQueryBuilder<T> Where(Expression<Func<T, bool>> predicate)
{
_filters.Add(predicate);
return this;
}
public DynamicQueryBuilder<T> WhereIf(bool condition, Expression<Func<T, bool>> predicate)
{
if (condition)
{
_filters.Add(predicate);
}
return this;
}
public DynamicQueryBuilder<T> OrderBy(Expression<Func<T, object>> keySelector)
{
_orderBy.Add((keySelector, false));
return this;
}
public DynamicQueryBuilder<T> OrderByDescending(Expression<Func<T, object>> keySelector)
{
_orderBy.Add((keySelector, true));
return this;
}
public DynamicQueryBuilder<T> WherePropertyEquals(string propertyName, object value)
{
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, propertyName);
var constant = Expression.Constant(value);
var equals = Expression.Equal(property, constant);
var lambda = Expression.Lambda<Func<T, bool>>(equals, parameter);
_filters.Add(lambda);
return this;
}
public DynamicQueryBuilder<T> WherePropertyContains(string propertyName, string value)
{
if (string.IsNullOrEmpty(value)) return this;
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, propertyName);
var constant = Expression.Constant(value);
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var contains = Expression.Call(property, containsMethod, constant);
var lambda = Expression.Lambda<Func<T, bool>>(contains, parameter);
_filters.Add(lambda);
return this;
}
public DynamicQueryBuilder<T> WherePropertyInRange<TProperty>(string propertyName, TProperty? min, TProperty? max)
where TProperty : struct, IComparable<TProperty>
{
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, propertyName);
Expression condition = null;
if (min.HasValue)
{
var minConstant = Expression.Constant(min.Value);
var greaterEqual = Expression.GreaterThanOrEqual(property, minConstant);
condition = condition == null ? greaterEqual : Expression.AndAlso(condition, greaterEqual);
}
if (max.HasValue)
{
var maxConstant = Expression.Constant(max.Value);
var lessEqual = Expression.LessThanOrEqual(property, maxConstant);
condition = condition == null ? lessEqual : Expression.AndAlso(condition, lessEqual);
}
if (condition != null)
{
var lambda = Expression.Lambda<Func<T, bool>>(condition, parameter);
_filters.Add(lambda);
}
return this;
}
public IQueryable<T> Build()
{
var result = _query;
// 应用所有过滤条件
foreach (var filter in _filters)
{
result = result.Where(filter);
}
// 应用排序
if (_orderBy.Any())
{
var firstOrder = _orderBy.First();
result = firstOrder.Descending
? result.OrderByDescending(firstOrder.KeySelector)
: result.OrderBy(firstOrder.KeySelector);
foreach (var order in _orderBy.Skip(1))
{
result = order.Descending
? ((IOrderedQueryable<T>)result).ThenByDescending(order.KeySelector)
: ((IOrderedQueryable<T>)result).ThenBy(order.KeySelector);
}
}
return result;
}
public List<T> ToList() => Build().ToList();
public T[] ToArray() => Build().ToArray();
public T First() => Build().First();
public T FirstOrDefault() => Build().FirstOrDefault();
public int Count() => Build().Count();
public bool Any() => Build().Any();
}
// 使用示例
public class DynamicQueryDemo
{
public static void DemonstrateDynamicQuery()
{
Console.WriteLine("\n=== 动态查询构建器演示 ===");
var students = new List<Student>
{
new Student { Id = 1, Name = "张三", Age = 20, Major = "计算机科学", GPA = 3.8 },
new Student { Id = 2, Name = "李四", Age = 21, Major = "软件工程", GPA = 3.6 },
new Student { Id = 3, Name = "王五", Age = 19, Major = "计算机科学", GPA = 3.9 },
new Student { Id = 4, Name = "赵六", Age = 22, Major = "信息系统", GPA = 3.4 },
new Student { Id = 5, Name = "钱七", Age = 20, Major = "软件工程", GPA = 3.7 }
}.AsQueryable();
// 动态构建查询
var queryBuilder = new DynamicQueryBuilder<Student>(students);
// 模拟用户输入的查询条件
string nameFilter = "";
string majorFilter = "计算机科学";
int? minAge = 19;
int? maxAge = 21;
double? minGPA = 3.7;
var result = queryBuilder
.WhereIf(!string.IsNullOrEmpty(nameFilter), s => s.Name.Contains(nameFilter))
.WhereIf(!string.IsNullOrEmpty(majorFilter), s => s.Major == majorFilter)
.WherePropertyInRange(nameof(Student.Age), minAge, maxAge)
.WherePropertyInRange(nameof(Student.GPA), minGPA, null)
.OrderByDescending(s => s.GPA)
.ToList();
Console.WriteLine("查询结果:");
foreach (var student in result)
{
Console.WriteLine($" {student.Name} - {student.Major} - {student.Age}岁 - GPA: {student.GPA}");
}
// 使用属性名动态查询
var dynamicResult = new DynamicQueryBuilder<Student>(students)
.WherePropertyEquals(nameof(Student.Major), "软件工程")
.WherePropertyContains(nameof(Student.Name), "")
.OrderBy(s => s.Name)
.ToList();
Console.WriteLine("\n动态属性查询结果:");
foreach (var student in dynamicResult)
{
Console.WriteLine($" {student.Name} - {student.Major}");
}
}
}
7. 总结
本章深入介绍了LINQ和表达式树的核心概念和实际应用:
核心概念
- LINQ基础:查询语法vs方法语法、延迟执行vs立即执行
- LINQ to Objects:过滤、投影、排序、分组、连接等操作
- 表达式树:构建、分析、修改和转换表达式树
- 自定义扩展:创建自定义LINQ扩展方法
重要技能
- 熟练使用各种LINQ操作符进行数据查询和处理
- 理解延迟执行的机制和性能影响
- 掌握表达式树的构建和操作技巧
- 能够创建自定义LINQ扩展方法
- 掌握LINQ性能优化技巧
最佳实践
- 避免多次枚举,适当使用缓存
- 正确使用Any()和Count()方法
- 选择合适的集合类型进行查询
- 合理使用并行LINQ提升性能
- 正确处理查询中的异常情况
实际应用
- 数据分析和报表生成
- 动态查询构建
- 复杂的业务逻辑处理
- 性能敏感的数据处理场景
LINQ和表达式树是.NET平台上强大的数据处理工具,掌握这些技术能够显著提高开发效率和代码质量。下一章我们将学习文件I/O和序列化。
2.2 排序和分组操作
public class LinqSortingGrouping
{
public static void DemonstrateSortingGrouping()
{
Console.WriteLine("\n=== LINQ排序和分组演示 ===");
var students = GetSampleStudents();
Console.WriteLine("\n--- 排序操作 ---");
DemonstrateSorting(students);
Console.WriteLine("\n--- 分组操作 ---");
DemonstrateGrouping(students);
Console.WriteLine("\n--- 复杂分组和排序 ---");
DemonstrateComplexGrouping(students);
}
private static List<Student> GetSampleStudents()
{
return new List<Student>
{
new Student { Id = 1, Name = "张三", Age = 20, Major = "计算机科学", GPA = 3.8 },
new Student { Id = 2, Name = "李四", Age = 22, Major = "软件工程", GPA = 3.6 },
new Student { Id = 3, Name = "王五", Age = 19, Major = "计算机科学", GPA = 3.9 },
new Student { Id = 4, Name = "赵六", Age = 21, Major = "信息系统", GPA = 3.4 },
new Student { Id = 5, Name = "钱七", Age = 23, Major = "软件工程", GPA = 3.7 },
new Student { Id = 6, Name = "孙八", Age = 20, Major = "计算机科学", GPA = 3.5 },
new Student { Id = 7, Name = "周九", Age = 22, Major = "信息系统", GPA = 3.8 },
new Student { Id = 8, Name = "吴十", Age = 21, Major = "软件工程", GPA = 3.9 }
};
}
private static void DemonstrateSorting(List<Student> students)
{
// 单字段升序排序
var sortedByAge = students.OrderBy(s => s.Age);
Console.WriteLine("按年龄升序排序:");
foreach (var student in sortedByAge)
{
Console.WriteLine($" {student.Name} - 年龄: {student.Age}");
}
// 单字段降序排序
var sortedByGPA = students.OrderByDescending(s => s.GPA);
Console.WriteLine("\n按GPA降序排序:");
foreach (var student in sortedByGPA)
{
Console.WriteLine($" {student.Name} - GPA: {student.GPA:F2}");
}
// 多字段排序
var multiSort = students
.OrderBy(s => s.Major) // 首先按专业排序
.ThenByDescending(s => s.GPA) // 然后按GPA降序
.ThenBy(s => s.Age); // 最后按年龄升序
Console.WriteLine("\n多字段排序(专业升序 -> GPA降序 -> 年龄升序):");
foreach (var student in multiSort)
{
Console.WriteLine($" {student.Major} | {student.Name} - GPA: {student.GPA:F2}, 年龄: {student.Age}");
}
// 自定义比较器排序
var customSort = students.OrderBy(s => s.Name, StringComparer.OrdinalIgnoreCase);
Console.WriteLine("\n按姓名排序(忽略大小写):");
foreach (var student in customSort)
{
Console.WriteLine($" {student.Name}");
}
}
private static void DemonstrateGrouping(List<Student> students)
{
// 按单个字段分组
var groupedByMajor = students.GroupBy(s => s.Major);
Console.WriteLine("按专业分组:");
foreach (var group in groupedByMajor)
{
Console.WriteLine($" {group.Key} ({group.Count()}人):");
foreach (var student in group)
{
Console.WriteLine($" {student.Name} - GPA: {student.GPA:F2}");
}
}
// 按多个字段分组
var groupedByMajorAndAge = students.GroupBy(s => new { s.Major, AgeGroup = s.Age < 21 ? "年轻" : "成熟" });
Console.WriteLine("\n按专业和年龄组分组:");
foreach (var group in groupedByMajorAndAge)
{
Console.WriteLine($" {group.Key.Major} - {group.Key.AgeGroup} ({group.Count()}人):");
foreach (var student in group)
{
Console.WriteLine($" {student.Name} - 年龄: {student.Age}");
}
}
// 分组后的聚合操作
var majorStatistics = students
.GroupBy(s => s.Major)
.Select(g => new
{
Major = g.Key,
Count = g.Count(),
AverageGPA = g.Average(s => s.GPA),
MaxGPA = g.Max(s => s.GPA),
MinGPA = g.Min(s => s.GPA),
Students = g.Select(s => s.Name).ToList()
});
Console.WriteLine("\n专业统计信息:");
foreach (var stat in majorStatistics)
{
Console.WriteLine($" {stat.Major}:");
Console.WriteLine($" 人数: {stat.Count}");
Console.WriteLine($" 平均GPA: {stat.AverageGPA:F2}");
Console.WriteLine($" 最高GPA: {stat.MaxGPA:F2}");
Console.WriteLine($" 最低GPA: {stat.MinGPA:F2}");
Console.WriteLine($" 学生: [{string.Join(", ", stat.Students)}]");
}
}
private static void DemonstrateComplexGrouping(List<Student> students)
{
// 复杂分组:按GPA等级分组
var groupedByGrade = students
.GroupBy(s => s.GPA >= 3.8 ? "优秀" : s.GPA >= 3.6 ? "良好" : "一般")
.OrderByDescending(g => g.Key); // 按等级排序
Console.WriteLine("按成绩等级分组:");
foreach (var group in groupedByGrade)
{
Console.WriteLine($" {group.Key} ({group.Count()}人):");
var sortedStudents = group.OrderByDescending(s => s.GPA);
foreach (var student in sortedStudents)
{
Console.WriteLine($" {student.Name} ({student.Major}) - GPA: {student.GPA:F2}");
}
}
// 嵌套分组:先按专业分组,再按年龄分组
var nestedGrouping = students
.GroupBy(s => s.Major)
.Select(majorGroup => new
{
Major = majorGroup.Key,
AgeGroups = majorGroup.GroupBy(s => s.Age < 21 ? "年轻" : "成熟")
.Select(ageGroup => new
{
AgeGroup = ageGroup.Key,
Students = ageGroup.OrderByDescending(s => s.GPA).ToList()
})
});
Console.WriteLine("\n嵌套分组(专业 -> 年龄组):");
foreach (var majorGroup in nestedGrouping)
{
Console.WriteLine($" {majorGroup.Major}:");
foreach (var ageGroup in majorGroup.AgeGroups)
{
Console.WriteLine($" {ageGroup.AgeGroup} ({ageGroup.Students.Count}人):");
foreach (var student in ageGroup.Students)
{
Console.WriteLine($" {student.Name} - GPA: {student.GPA:F2}, 年龄: {student.Age}");
}
}
}
// 分组后的复杂查询:找出每个专业GPA前2名的学生
var topStudentsByMajor = students
.GroupBy(s => s.Major)
.SelectMany(g => g.OrderByDescending(s => s.GPA).Take(2)
.Select((student, index) => new
{
Major = g.Key,
Rank = index + 1,
Student = student
}));
Console.WriteLine("\n每个专业前2名学生:");
foreach (var item in topStudentsByMajor.OrderBy(x => x.Major).ThenBy(x => x.Rank))
{
Console.WriteLine($" {item.Major} 第{item.Rank}名: {item.Student.Name} - GPA: {item.Student.GPA:F2}");
}
}
}