本章目标

通过本章学习,你将掌握: - LINQ的概念和优势 - 查询语法和方法语法 - 标准查询操作符 - LINQ to Objects - 延迟执行和立即执行 - 自定义LINQ扩展 - LINQ性能优化

1. LINQ基础

1.1 LINQ概念

LINQ(Language Integrated Query)是.NET Framework中的一项技术,它将查询功能直接集成到C#语言中,提供统一的查询语法来处理各种数据源。

using System;
using System.Collections.Generic;
using System.Linq;

// 示例数据模型
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 DateTime EnrollmentDate { get; set; }
    public List<string> Courses { get; set; }
    
    public Student()
    {
        Courses = new List<string>();
    }
    
    public Student(int id, string name, int age, string major, double gpa, DateTime enrollmentDate)
    {
        Id = id;
        Name = name;
        Age = age;
        Major = major;
        GPA = gpa;
        EnrollmentDate = enrollmentDate;
        Courses = new List<string>();
    }
    
    public override string ToString()
    {
        return $"{Name}(ID:{Id}, {Age}岁, {Major}, GPA:{GPA:F2})";
    }
}

public class Course
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Department { get; set; }
    public int Credits { get; set; }
    public string Instructor { get; set; }
    public List<int> EnrolledStudentIds { get; set; }
    
    public Course()
    {
        EnrolledStudentIds = new List<int>();
    }
    
    public Course(int id, string name, string department, int credits, string instructor)
    {
        Id = id;
        Name = name;
        Department = department;
        Credits = credits;
        Instructor = instructor;
        EnrolledStudentIds = new List<int>();
    }
    
    public override string ToString()
    {
        return $"{Name} ({Department}, {Credits}学分, 教师:{Instructor})";
    }
}

// 数据生成器
public static class DataGenerator
{
    public static List<Student> GenerateStudents()
    {
        return new List<Student>
        {
            new Student(1, "张三", 20, "计算机科学", 3.8, new DateTime(2021, 9, 1)),
            new Student(2, "李四", 21, "数学", 3.6, new DateTime(2020, 9, 1)),
            new Student(3, "王五", 19, "物理", 3.9, new DateTime(2022, 9, 1)),
            new Student(4, "赵六", 22, "计算机科学", 3.4, new DateTime(2019, 9, 1)),
            new Student(5, "钱七", 20, "化学", 3.7, new DateTime(2021, 9, 1)),
            new Student(6, "孙八", 23, "数学", 3.2, new DateTime(2018, 9, 1)),
            new Student(7, "周九", 19, "物理", 3.95, new DateTime(2022, 9, 1)),
            new Student(8, "吴十", 21, "计算机科学", 3.85, new DateTime(2020, 9, 1)),
            new Student(9, "郑一", 20, "化学", 3.3, new DateTime(2021, 9, 1)),
            new Student(10, "王二", 22, "数学", 3.75, new DateTime(2019, 9, 1))
        };
    }
    
    public static List<Course> GenerateCourses()
    {
        return new List<Course>
        {
            new Course(101, "数据结构", "计算机科学", 3, "张教授"),
            new Course(102, "算法分析", "计算机科学", 3, "李教授"),
            new Course(103, "高等数学", "数学", 4, "王教授"),
            new Course(104, "线性代数", "数学", 3, "赵教授"),
            new Course(105, "普通物理", "物理", 4, "钱教授"),
            new Course(106, "量子力学", "物理", 3, "孙教授"),
            new Course(107, "有机化学", "化学", 3, "周教授"),
            new Course(108, "无机化学", "化学", 3, "吴教授"),
            new Course(109, "数据库系统", "计算机科学", 3, "郑教授"),
            new Course(110, "概率统计", "数学", 3, "王教授")
        };
    }
    
    public static void AssignCoursesToStudents(List<Student> students, List<Course> courses)
    {
        var random = new Random(42); // 固定种子确保结果一致
        
        foreach (var student in students)
        {
            // 根据专业分配相关课程
            var relevantCourses = courses.Where(c => 
                c.Department == student.Major || 
                c.Department == "数学" || // 数学是基础课程
                random.NextDouble() < 0.3 // 30%概率选择其他课程
            ).ToList();
            
            // 每个学生选择3-6门课程
            int courseCount = random.Next(3, 7);
            var selectedCourses = relevantCourses
                .OrderBy(x => random.Next())
                .Take(courseCount)
                .ToList();
            
            foreach (var course in selectedCourses)
            {
                student.Courses.Add(course.Name);
                course.EnrolledStudentIds.Add(student.Id);
            }
        }
    }
}

1.2 查询语法 vs 方法语法

public class LinqSyntaxDemo
{
    public static void DemonstrateSyntaxDifferences()
    {
        Console.WriteLine("=== LINQ语法对比演示 ===");
        
        var students = DataGenerator.GenerateStudents();
        var courses = DataGenerator.GenerateCourses();
        DataGenerator.AssignCoursesToStudents(students, courses);
        
        Console.WriteLine("\n--- 查询语法 vs 方法语法 ---");
        
        // 1. 基本过滤
        Console.WriteLine("\n1. 查找计算机科学专业的学生:");
        
        // 查询语法
        var csStudentsQuery = from student in students
                             where student.Major == "计算机科学"
                             select student;
        
        // 方法语法
        var csStudentsMethod = students.Where(s => s.Major == "计算机科学");
        
        Console.WriteLine("查询语法结果:");
        foreach (var student in csStudentsQuery)
        {
            Console.WriteLine($"  {student}");
        }
        
        Console.WriteLine("方法语法结果:");
        foreach (var student in csStudentsMethod)
        {
            Console.WriteLine($"  {student}");
        }
        
        // 2. 排序
        Console.WriteLine("\n2. 按GPA降序排列:");
        
        // 查询语法
        var sortedQuery = from student in students
                         orderby student.GPA descending
                         select student;
        
        // 方法语法
        var sortedMethod = students.OrderByDescending(s => s.GPA);
        
        Console.WriteLine("查询语法结果(前5名):");
        foreach (var student in sortedQuery.Take(5))
        {
            Console.WriteLine($"  {student}");
        }
        
        // 3. 投影(选择特定字段)
        Console.WriteLine("\n3. 投影 - 只选择姓名和GPA:");
        
        // 查询语法
        var projectionQuery = from student in students
                             where student.GPA > 3.5
                             select new { student.Name, student.GPA };
        
        // 方法语法
        var projectionMethod = students
            .Where(s => s.GPA > 3.5)
            .Select(s => new { s.Name, s.GPA });
        
        Console.WriteLine("查询语法结果:");
        foreach (var item in projectionQuery)
        {
            Console.WriteLine($"  {item.Name}: {item.GPA:F2}");
        }
        
        // 4. 分组
        Console.WriteLine("\n4. 按专业分组:");
        
        // 查询语法
        var groupQuery = from student in students
                        group student by student.Major into majorGroup
                        select new
                        {
                            Major = majorGroup.Key,
                            Count = majorGroup.Count(),
                            AvgGPA = majorGroup.Average(s => s.GPA)
                        };
        
        // 方法语法
        var groupMethod = students
            .GroupBy(s => s.Major)
            .Select(g => new
            {
                Major = g.Key,
                Count = g.Count(),
                AvgGPA = g.Average(s => s.GPA)
            });
        
        Console.WriteLine("查询语法结果:");
        foreach (var group in groupQuery)
        {
            Console.WriteLine($"  {group.Major}: {group.Count}人, 平均GPA: {group.AvgGPA:F2}");
        }
        
        // 5. 复杂查询
        Console.WriteLine("\n5. 复杂查询 - 每个专业GPA最高的学生:");
        
        // 查询语法
        var topStudentsQuery = from student in students
                              group student by student.Major into majorGroup
                              select majorGroup.OrderByDescending(s => s.GPA).First();
        
        // 方法语法
        var topStudentsMethod = students
            .GroupBy(s => s.Major)
            .Select(g => g.OrderByDescending(s => s.GPA).First());
        
        Console.WriteLine("查询语法结果:");
        foreach (var student in topStudentsQuery)
        {
            Console.WriteLine($"  {student}");
        }
    }
}

2. 标准查询操作符

2.1 过滤操作符

public class FilteringOperatorsDemo
{
    public static void DemonstrateFilteringOperators()
    {
        Console.WriteLine("\n=== 过滤操作符演示 ===");
        
        var students = DataGenerator.GenerateStudents();
        var numbers = Enumerable.Range(1, 20).ToList();
        
        // Where - 基本过滤
        Console.WriteLine("\n--- Where操作符 ---");
        var excellentStudents = students.Where(s => s.GPA >= 3.8);
        Console.WriteLine($"优秀学生 (GPA >= 3.8) ({excellentStudents.Count()}人):");
        foreach (var student in excellentStudents)
        {
            Console.WriteLine($"  {student}");
        }
        
        // Where with index
        var evenIndexStudents = students.Where((student, index) => index % 2 == 0);
        Console.WriteLine($"\n偶数索引位置的学生 ({evenIndexStudents.Count()}人):");
        foreach (var student in evenIndexStudents)
        {
            Console.WriteLine($"  {student}");
        }
        
        // OfType - 类型过滤
        Console.WriteLine("\n--- OfType操作符 ---");
        object[] mixedArray = { 1, "hello", 2.5, "world", 42, 3.14, "test" };
        var strings = mixedArray.OfType<string>();
        var numbers2 = mixedArray.OfType<int>();
        var doubles = mixedArray.OfType<double>();
        
        Console.WriteLine($"字符串: [{string.Join(", ", strings)}]");
        Console.WriteLine($"整数: [{string.Join(", ", numbers2)}]");
        Console.WriteLine($"双精度: [{string.Join(", ", doubles)}]");
        
        // Distinct - 去重
        Console.WriteLine("\n--- Distinct操作符 ---");
        var majors = students.Select(s => s.Major).Distinct();
        Console.WriteLine($"所有专业: [{string.Join(", ", majors)}]");
        
        var duplicateNumbers = new[] { 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5 };
        var uniqueNumbers = duplicateNumbers.Distinct();
        Console.WriteLine($"去重前: [{string.Join(", ", duplicateNumbers)}]");
        Console.WriteLine($"去重后: [{string.Join(", ", uniqueNumbers)}]");
        
        // Take / Skip - 分页
        Console.WriteLine("\n--- Take/Skip操作符 ---");
        var firstFive = students.Take(5);
        var skipFirstThree = students.Skip(3);
        var page2 = students.Skip(5).Take(3); // 第二页,每页3条
        
        Console.WriteLine($"前5个学生:");
        foreach (var student in firstFive)
        {
            Console.WriteLine($"  {student}");
        }
        
        Console.WriteLine($"\n第二页学生 (跳过5个,取3个):");
        foreach (var student in page2)
        {
            Console.WriteLine($"  {student}");
        }
        
        // TakeWhile / SkipWhile - 条件性获取/跳过
        Console.WriteLine("\n--- TakeWhile/SkipWhile操作符 ---");
        var sortedByGPA = students.OrderByDescending(s => s.GPA);
        var topPerformers = sortedByGPA.TakeWhile(s => s.GPA >= 3.7);
        var afterTopPerformers = sortedByGPA.SkipWhile(s => s.GPA >= 3.7);
        
        Console.WriteLine($"顶尖学生 (GPA >= 3.7,连续):");
        foreach (var student in topPerformers)
        {
            Console.WriteLine($"  {student}");
        }
        
        Console.WriteLine($"\n其余学生:");
        foreach (var student in afterTopPerformers)
        {
            Console.WriteLine($"  {student}");
        }
    }
}

2.2 投影操作符

public class ProjectionOperatorsDemo
{
    public static void DemonstrateProjectionOperators()
    {
        Console.WriteLine("\n=== 投影操作符演示 ===");
        
        var students = DataGenerator.GenerateStudents();
        var courses = DataGenerator.GenerateCourses();
        DataGenerator.AssignCoursesToStudents(students, courses);
        
        // Select - 基本投影
        Console.WriteLine("\n--- Select操作符 ---");
        var studentNames = students.Select(s => s.Name);
        Console.WriteLine($"学生姓名: [{string.Join(", ", studentNames)}]");
        
        var studentInfo = students.Select(s => new
        {
            s.Name,
            s.Age,
            Grade = s.GPA >= 3.8 ? "优秀" : s.GPA >= 3.5 ? "良好" : s.GPA >= 3.0 ? "及格" : "不及格"
        });
        
        Console.WriteLine("\n学生信息摘要:");
        foreach (var info in studentInfo)
        {
            Console.WriteLine($"  {info.Name}({info.Age}岁): {info.Grade}");
        }
        
        // Select with index
        var indexedStudents = students.Select((student, index) => new
        {
            Index = index + 1,
            student.Name,
            student.Major
        });
        
        Console.WriteLine("\n带索引的学生列表:");
        foreach (var item in indexedStudents.Take(5))
        {
            Console.WriteLine($"  {item.Index}. {item.Name} - {item.Major}");
        }
        
        // SelectMany - 扁平化投影
        Console.WriteLine("\n--- SelectMany操作符 ---");
        
        // 获取所有学生的所有课程
        var allCourses = students.SelectMany(s => s.Courses);
        Console.WriteLine($"所有课程 (包含重复): [{string.Join(", ", allCourses)}]");
        
        var uniqueCourses = students.SelectMany(s => s.Courses).Distinct();
        Console.WriteLine($"\n所有课程 (去重): [{string.Join(", ", uniqueCourses)}]");
        
        // SelectMany with result selector
        var studentCourseMapping = students.SelectMany(
            student => student.Courses,
            (student, course) => new
            {
                StudentName = student.Name,
                CourseName = course,
                student.Major
            }
        );
        
        Console.WriteLine("\n学生-课程映射 (前10条):");
        foreach (var mapping in studentCourseMapping.Take(10))
        {
            Console.WriteLine($"  {mapping.StudentName} ({mapping.Major}) -> {mapping.CourseName}");
        }
        
        // 复杂投影示例
        Console.WriteLine("\n--- 复杂投影示例 ---");
        
        var departmentStats = students
            .GroupBy(s => s.Major)
            .Select(g => new
            {
                Department = g.Key,
                StudentCount = g.Count(),
                AverageAge = g.Average(s => s.Age),
                AverageGPA = g.Average(s => s.GPA),
                TopStudent = g.OrderByDescending(s => s.GPA).First().Name,
                TotalCourses = g.SelectMany(s => s.Courses).Distinct().Count()
            })
            .OrderByDescending(d => d.AverageGPA);
        
        Console.WriteLine("专业统计信息:");
        foreach (var stat in departmentStats)
        {
            Console.WriteLine($"  {stat.Department}:");
            Console.WriteLine($"    学生数: {stat.StudentCount}");
            Console.WriteLine($"    平均年龄: {stat.AverageAge:F1}岁");
            Console.WriteLine($"    平均GPA: {stat.AverageGPA:F2}");
            Console.WriteLine($"    最优学生: {stat.TopStudent}");
            Console.WriteLine($"    课程总数: {stat.TotalCourses}");
        }
    }
}

2.3 排序操作符

public class OrderingOperatorsDemo
{
    public static void DemonstrateOrderingOperators()
    {
        Console.WriteLine("\n=== 排序操作符演示 ===");
        
        var students = DataGenerator.GenerateStudents();
        
        // OrderBy / OrderByDescending - 单字段排序
        Console.WriteLine("\n--- 单字段排序 ---");
        
        var byGPAAsc = students.OrderBy(s => s.GPA);
        Console.WriteLine("按GPA升序:");
        foreach (var student in byGPAAsc.Take(5))
        {
            Console.WriteLine($"  {student}");
        }
        
        var byGPADesc = students.OrderByDescending(s => s.GPA);
        Console.WriteLine("\n按GPA降序:");
        foreach (var student in byGPADesc.Take(5))
        {
            Console.WriteLine($"  {student}");
        }
        
        // ThenBy / ThenByDescending - 多字段排序
        Console.WriteLine("\n--- 多字段排序 ---");
        
        var multiSort = students
            .OrderBy(s => s.Major)           // 首先按专业排序
            .ThenByDescending(s => s.GPA)    // 然后按GPA降序
            .ThenBy(s => s.Age);             // 最后按年龄升序
        
        Console.WriteLine("按专业(升序) -> GPA(降序) -> 年龄(升序):");
        foreach (var student in multiSort)
        {
            Console.WriteLine($"  {student}");
        }
        
        // Reverse - 反转顺序
        Console.WriteLine("\n--- 反转排序 ---");
        
        var originalOrder = students.Take(5);
        var reversedOrder = originalOrder.Reverse();
        
        Console.WriteLine("原始顺序:");
        foreach (var student in originalOrder)
        {
            Console.WriteLine($"  {student}");
        }
        
        Console.WriteLine("\n反转后:");
        foreach (var student in reversedOrder)
        {
            Console.WriteLine($"  {student}");
        }
        
        // 自定义比较器排序
        Console.WriteLine("\n--- 自定义比较器排序 ---");
        
        // 按姓名长度排序
        var byNameLength = students.OrderBy(s => s.Name.Length).ThenBy(s => s.Name);
        Console.WriteLine("按姓名长度排序:");
        foreach (var student in byNameLength)
        {
            Console.WriteLine($"  {student.Name}({student.Name.Length}字符) - {student.Major}");
        }
        
        // 使用IComparer接口
        var customComparer = Comparer<Student>.Create((s1, s2) =>
        {
            // 首先比较专业
            int majorComparison = string.Compare(s1.Major, s2.Major);
            if (majorComparison != 0) return majorComparison;
            
            // 然后比较GPA(降序)
            int gpaComparison = s2.GPA.CompareTo(s1.GPA);
            if (gpaComparison != 0) return gpaComparison;
            
            // 最后比较姓名
            return string.Compare(s1.Name, s2.Name);
        });
        
        var customSorted = students.OrderBy(s => s, customComparer);
        Console.WriteLine("\n使用自定义比较器排序:");
        foreach (var student in customSorted)
        {
            Console.WriteLine($"  {student}");
        }
    }
}

2.4 分组操作符

public class GroupingOperatorsDemo
{
    public static void DemonstrateGroupingOperators()
    {
        Console.WriteLine("\n=== 分组操作符演示 ===");
        
        var students = DataGenerator.GenerateStudents();
        var courses = DataGenerator.GenerateCourses();
        DataGenerator.AssignCoursesToStudents(students, courses);
        
        // GroupBy - 基本分组
        Console.WriteLine("\n--- 基本分组 ---");
        
        var groupsByMajor = students.GroupBy(s => s.Major);
        Console.WriteLine("按专业分组:");
        foreach (var group in groupsByMajor)
        {
            Console.WriteLine($"  {group.Key} ({group.Count()}人):");
            foreach (var student in group)
            {
                Console.WriteLine($"    {student}");
            }
        }
        
        // GroupBy with result selector
        Console.WriteLine("\n--- 分组结果投影 ---");
        
        var majorSummary = students.GroupBy(
            s => s.Major,
            (major, studentsInMajor) => new
            {
                Major = major,
                Count = studentsInMajor.Count(),
                AverageGPA = studentsInMajor.Average(s => s.GPA),
                AverageAge = studentsInMajor.Average(s => s.Age),
                TopGPA = studentsInMajor.Max(s => s.GPA),
                Students = studentsInMajor.Select(s => s.Name).ToList()
            }
        );
        
        Console.WriteLine("专业摘要:");
        foreach (var summary in majorSummary.OrderByDescending(s => s.AverageGPA))
        {
            Console.WriteLine($"  {summary.Major}:");
            Console.WriteLine($"    人数: {summary.Count}");
            Console.WriteLine($"    平均GPA: {summary.AverageGPA:F2}");
            Console.WriteLine($"    平均年龄: {summary.AverageAge:F1}岁");
            Console.WriteLine($"    最高GPA: {summary.TopGPA:F2}");
            Console.WriteLine($"    学生: [{string.Join(", ", summary.Students)}]");
        }
        
        // 多键分组
        Console.WriteLine("\n--- 多键分组 ---");
        
        var groupsByMajorAndAge = students.GroupBy(s => new { s.Major, AgeGroup = s.Age < 21 ? "年轻" : "成熟" });
        Console.WriteLine("按专业和年龄组分组:");
        foreach (var group in groupsByMajorAndAge.OrderBy(g => g.Key.Major).ThenBy(g => g.Key.AgeGroup))
        {
            Console.WriteLine($"  {group.Key.Major} - {group.Key.AgeGroup} ({group.Count()}人):");
            foreach (var student in group)
            {
                Console.WriteLine($"    {student}");
            }
        }
        
        // 嵌套分组
        Console.WriteLine("\n--- 嵌套分组 ---");
        
        var nestedGroups = 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.ToList(),
                        Count = ageGroup.Count(),
                        AvgGPA = ageGroup.Average(s => s.GPA)
                    })
                    .ToList()
            });
        
        Console.WriteLine("嵌套分组结果:");
        foreach (var majorGroup in nestedGroups)
        {
            Console.WriteLine($"  {majorGroup.Major}:");
            foreach (var ageGroup in majorGroup.AgeGroups)
            {
                Console.WriteLine($"    {ageGroup.AgeGroup} ({ageGroup.Count}人, 平均GPA: {ageGroup.AvgGPA:F2}):");
                foreach (var student in ageGroup.Students)
                {
                    Console.WriteLine($"      {student}");
                }
            }
        }
        
        // ToLookup - 创建查找表
        Console.WriteLine("\n--- ToLookup操作符 ---");
        
        var majorLookup = students.ToLookup(s => s.Major);
        Console.WriteLine("使用Lookup查找:");
        Console.WriteLine($"计算机科学专业学生 ({majorLookup["计算机科学"].Count()}人):");
        foreach (var student in majorLookup["计算机科学"])
        {
            Console.WriteLine($"  {student}");
        }
        
        Console.WriteLine($"\n数学专业学生 ({majorLookup["数学"].Count()}人):");
        foreach (var student in majorLookup["数学"])
        {
            Console.WriteLine($"  {student}");
        }
        
        // 检查不存在的键
        Console.WriteLine($"\n生物专业学生 ({majorLookup["生物"].Count()}人):"); // 返回空序列,不会抛异常
    }
}

3. 聚合操作符

3.1 数值聚合

public class AggregateOperatorsDemo
{
    public static void DemonstrateAggregateOperators()
    {
        Console.WriteLine("\n=== 聚合操作符演示 ===");
        
        var students = DataGenerator.GenerateStudents();
        var numbers = Enumerable.Range(1, 10).ToList();
        
        // Count / LongCount
        Console.WriteLine("\n--- 计数操作符 ---");
        
        var totalStudents = students.Count();
        var excellentStudents = students.Count(s => s.GPA >= 3.8);
        var csStudents = students.Count(s => s.Major == "计算机科学");
        
        Console.WriteLine($"总学生数: {totalStudents}");
        Console.WriteLine($"优秀学生数 (GPA >= 3.8): {excellentStudents}");
        Console.WriteLine($"计算机科学专业学生数: {csStudents}");
        
        // Sum
        Console.WriteLine("\n--- 求和操作符 ---");
        
        var totalAge = students.Sum(s => s.Age);
        var totalGPA = students.Sum(s => s.GPA);
        var numberSum = numbers.Sum();
        
        Console.WriteLine($"所有学生年龄总和: {totalAge}岁");
        Console.WriteLine($"所有学生GPA总和: {totalGPA:F2}");
        Console.WriteLine($"数字1-10的和: {numberSum}");
        
        // Average
        Console.WriteLine("\n--- 平均值操作符 ---");
        
        var avgAge = students.Average(s => s.Age);
        var avgGPA = students.Average(s => s.GPA);
        var avgByMajor = students
            .GroupBy(s => s.Major)
            .Select(g => new { Major = g.Key, AvgGPA = g.Average(s => s.GPA) })
            .OrderByDescending(x => x.AvgGPA);
        
        Console.WriteLine($"平均年龄: {avgAge:F1}岁");
        Console.WriteLine($"平均GPA: {avgGPA:F2}");
        Console.WriteLine("各专业平均GPA:");
        foreach (var item in avgByMajor)
        {
            Console.WriteLine($"  {item.Major}: {item.AvgGPA:F2}");
        }
        
        // Min / Max
        Console.WriteLine("\n--- 最值操作符 ---");
        
        var minAge = students.Min(s => s.Age);
        var maxAge = students.Max(s => s.Age);
        var minGPA = students.Min(s => s.GPA);
        var maxGPA = students.Max(s => s.GPA);
        
        var youngestStudent = students.First(s => s.Age == minAge);
        var oldestStudent = students.First(s => s.Age == maxAge);
        var lowestGPAStudent = students.First(s => s.GPA == minGPA);
        var highestGPAStudent = students.First(s => s.GPA == maxGPA);
        
        Console.WriteLine($"最小年龄: {minAge}岁 ({youngestStudent.Name})");
        Console.WriteLine($"最大年龄: {maxAge}岁 ({oldestStudent.Name})");
        Console.WriteLine($"最低GPA: {minGPA:F2} ({lowestGPAStudent.Name})");
        Console.WriteLine($"最高GPA: {maxGPA:F2} ({highestGPAStudent.Name})");
        
        // Aggregate - 自定义聚合
        Console.WriteLine("\n--- 自定义聚合操作符 ---");
        
        // 计算所有学生姓名的连接
        var allNames = students.Aggregate(
            "",
            (acc, student) => acc + (acc == "" ? "" : ", ") + student.Name
        );
        Console.WriteLine($"所有学生姓名: {allNames}");
        
        // 计算数字的乘积
        var product = numbers.Take(5).Aggregate(1, (acc, num) => acc * num);
        Console.WriteLine($"1-5的乘积: {product}");
        
        // 找到最长的姓名
        var longestName = students.Aggregate(
            (s1, s2) => s1.Name.Length >= s2.Name.Length ? s1 : s2
        );
        Console.WriteLine($"姓名最长的学生: {longestName.Name} ({longestName.Name.Length}字符)");
        
        // 复杂聚合:计算每个专业的统计信息
        var majorStats = students
            .GroupBy(s => s.Major)
            .Select(g => g.Aggregate(
                new { Count = 0, TotalAge = 0, TotalGPA = 0.0, Names = new List<string>() },
                (acc, student) => new
                {
                    Count = acc.Count + 1,
                    TotalAge = acc.TotalAge + student.Age,
                    TotalGPA = acc.TotalGPA + student.GPA,
                    Names = acc.Names.Concat(new[] { student.Name }).ToList()
                },
                acc => new
                {
                    Major = g.Key,
                    Count = acc.Count,
                    AvgAge = (double)acc.TotalAge / acc.Count,
                    AvgGPA = acc.TotalGPA / acc.Count,
                    Names = acc.Names
                }
            ));
        
        Console.WriteLine("\n专业统计 (使用Aggregate):");
        foreach (var stat in majorStats.OrderByDescending(s => s.AvgGPA))
        {
            Console.WriteLine($"  {stat.Major}:");
            Console.WriteLine($"    人数: {stat.Count}");
            Console.WriteLine($"    平均年龄: {stat.AvgAge:F1}岁");
            Console.WriteLine($"    平均GPA: {stat.AvgGPA:F2}");
            Console.WriteLine($"    学生: [{string.Join(", ", stat.Names)}]");
        }
    }
}

3.2 集合操作符

public class SetOperatorsDemo
{
    public static void DemonstrateSetOperators()
    {
        Console.WriteLine("\n=== 集合操作符演示 ===");
        
        var students = DataGenerator.GenerateStudents();
        var courses = DataGenerator.GenerateCourses();
        DataGenerator.AssignCoursesToStudents(students, courses);
        
        // 准备测试数据
        var csStudents = students.Where(s => s.Major == "计算机科学").ToList();
        var mathStudents = students.Where(s => s.Major == "数学").ToList();
        var excellentStudents = students.Where(s => s.GPA >= 3.7).ToList();
        
        var set1 = new[] { 1, 2, 3, 4, 5 };
        var set2 = new[] { 4, 5, 6, 7, 8 };
        var set3 = new[] { 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 };
        
        // Union - 并集
        Console.WriteLine("\n--- Union操作符 ---");
        
        var unionNumbers = set1.Union(set2);
        Console.WriteLine($"集合1: [{string.Join(", ", set1)}]");
        Console.WriteLine($"集合2: [{string.Join(", ", set2)}]");
        Console.WriteLine($"并集: [{string.Join(", ", unionNumbers)}]");
        
        var csOrMathStudents = csStudents.Union(mathStudents, new StudentComparer());
        Console.WriteLine($"\n计算机科学或数学专业学生 ({csOrMathStudents.Count()}人):");
        foreach (var student in csOrMathStudents)
        {
            Console.WriteLine($"  {student}");
        }
        
        // Intersect - 交集
        Console.WriteLine("\n--- Intersect操作符 ---");
        
        var intersectNumbers = set1.Intersect(set2);
        Console.WriteLine($"交集: [{string.Join(", ", intersectNumbers)}]");
        
        var csAndExcellentStudents = csStudents.Intersect(excellentStudents, new StudentComparer());
        Console.WriteLine($"\n计算机科学且优秀的学生 ({csAndExcellentStudents.Count()}人):");
        foreach (var student in csAndExcellentStudents)
        {
            Console.WriteLine($"  {student}");
        }
        
        // Except - 差集
        Console.WriteLine("\n--- Except操作符 ---");
        
        var exceptNumbers = set1.Except(set2);
        Console.WriteLine($"差集 (set1 - set2): [{string.Join(", ", exceptNumbers)}]");
        
        var csButNotExcellentStudents = csStudents.Except(excellentStudents, new StudentComparer());
        Console.WriteLine($"\n计算机科学但不优秀的学生 ({csButNotExcellentStudents.Count()}人):");
        foreach (var student in csButNotExcellentStudents)
        {
            Console.WriteLine($"  {student}");
        }
        
        // Distinct - 去重
        Console.WriteLine("\n--- Distinct操作符 ---");
        
        var distinctNumbers = set3.Distinct();
        Console.WriteLine($"原集合: [{string.Join(", ", set3)}]");
        Console.WriteLine($"去重后: [{string.Join(", ", distinctNumbers)}]");
        
        // 课程去重示例
        var allStudentCourses = students.SelectMany(s => s.Courses);
        var uniqueCourses = allStudentCourses.Distinct();
        Console.WriteLine($"\n所有学生选课 (含重复): {allStudentCourses.Count()}门次");
        Console.WriteLine($"不重复课程: {uniqueCourses.Count()}门");
        Console.WriteLine($"课程列表: [{string.Join(", ", uniqueCourses)}]");
        
        // SequenceEqual - 序列相等比较
        Console.WriteLine("\n--- SequenceEqual操作符 ---");
        
        var seq1 = new[] { 1, 2, 3, 4, 5 };
        var seq2 = new[] { 1, 2, 3, 4, 5 };
        var seq3 = new[] { 5, 4, 3, 2, 1 };
        
        Console.WriteLine($"序列1: [{string.Join(", ", seq1)}]");
        Console.WriteLine($"序列2: [{string.Join(", ", seq2)}]");
        Console.WriteLine($"序列3: [{string.Join(", ", seq3)}]");
        Console.WriteLine($"seq1 == seq2: {seq1.SequenceEqual(seq2)}");
        Console.WriteLine($"seq1 == seq3: {seq1.SequenceEqual(seq3)}");
        Console.WriteLine($"seq1 == seq3.Reverse(): {seq1.SequenceEqual(seq3.Reverse())}");
        
        // 复杂集合操作示例
        Console.WriteLine("\n--- 复杂集合操作示例 ---");
        
        // 找出每个专业独有的课程
        var majorCourses = students
            .GroupBy(s => s.Major)
            .ToDictionary(g => g.Key, g => g.SelectMany(s => s.Courses).Distinct().ToList());
        
        Console.WriteLine("各专业的课程:");
        foreach (var kvp in majorCourses)
        {
            Console.WriteLine($"  {kvp.Key}: [{string.Join(", ", kvp.Value)}]");
        }
        
        // 找出所有专业都有的公共课程
        var commonCourses = majorCourses.Values
            .Aggregate((courses1, courses2) => courses1.Intersect(courses2).ToList());
        
        Console.WriteLine($"\n所有专业的公共课程: [{string.Join(", ", commonCourses)}]");
        
        // 找出只有一个专业有的独特课程
        var allCoursesList = majorCourses.Values.SelectMany(courses => courses).Distinct().ToList();
        var uniqueToOneMajor = allCoursesList.Where(course =>
            majorCourses.Values.Count(courses => courses.Contains(course)) == 1
        ).ToList();
        
        Console.WriteLine($"\n只有一个专业有的课程: [{string.Join(", ", uniqueToOneMajor)}]");
    }
}

// 自定义学生比较器
public class StudentComparer : IEqualityComparer<Student>
{
    public bool Equals(Student x, Student y)
    {
        if (x == null && y == null) return true;
        if (x == null || y == null) return false;
        return x.Id == y.Id;
    }
    
    public int GetHashCode(Student obj)
    {
        return obj?.Id.GetHashCode() ?? 0;
    }
}

4. 延迟执行和立即执行

4.1 延迟执行演示

public class DeferredExecutionDemo
{
    public static void DemonstrateDeferredExecution()
    {
        Console.WriteLine("\n=== 延迟执行演示 ===");
        
        // 延迟执行示例
        Console.WriteLine("\n--- 延迟执行 ---");
        
        var numbers = new List<int> { 1, 2, 3, 4, 5 };
        Console.WriteLine($"初始数据: [{string.Join(", ", numbers)}]");
        
        // 创建查询(此时不执行)
        var evenNumbers = numbers.Where(n =>
        {
            Console.WriteLine($"  检查数字: {n}");
            return n % 2 == 0;
        });
        
        Console.WriteLine("\n查询已创建,但尚未执行");
        
        // 修改原始数据
        numbers.Add(6);
        numbers.Add(7);
        numbers.Add(8);
        Console.WriteLine($"\n添加数据后: [{string.Join(", ", numbers)}]");
        
        // 第一次执行查询
        Console.WriteLine("\n第一次执行查询:");
        var result1 = evenNumbers.ToList();
        Console.WriteLine($"结果: [{string.Join(", ", result1)}]");
        
        // 再次修改数据
        numbers.Add(9);
        numbers.Add(10);
        Console.WriteLine($"\n再次添加数据后: [{string.Join(", ", numbers)}]");
        
        // 第二次执行查询
        Console.WriteLine("\n第二次执行查询:");
        var result2 = evenNumbers.ToList();
        Console.WriteLine($"结果: [{string.Join(", ", result2)}]");
        
        // 立即执行示例
        Console.WriteLine("\n--- 立即执行 ---");
        
        var numbers2 = new List<int> { 1, 2, 3, 4, 5 };
        Console.WriteLine($"初始数据: [{string.Join(", ", numbers2)}]");
        
        // 立即执行(使用ToList())
        var evenNumbers2 = numbers2.Where(n =>
        {
            Console.WriteLine($"  检查数字: {n}");
            return n % 2 == 0;
        }).ToList(); // 立即执行
        
        Console.WriteLine($"立即执行结果: [{string.Join(", ", evenNumbers2)}]");
        
        // 修改原始数据
        numbers2.Add(6);
        numbers2.Add(7);
        Console.WriteLine($"\n添加数据后: [{string.Join(", ", numbers2)}]");
        Console.WriteLine($"立即执行的结果不变: [{string.Join(", ", evenNumbers2)}]");
        
        // 延迟执行的陷阱
        Console.WriteLine("\n--- 延迟执行的陷阱 ---");
        
        var source = Enumerable.Range(1, 5);
        var query = source.Select(x =>
        {
            Console.WriteLine($"  处理: {x}");
            return x * x;
        });
        
        Console.WriteLine("\n多次枚举同一个查询:");
        Console.WriteLine("第一次枚举:");
        foreach (var item in query.Take(3))
        {
            Console.WriteLine($"    结果: {item}");
        }
        
        Console.WriteLine("\n第二次枚举:");
        foreach (var item in query.Take(3))
        {
            Console.WriteLine($"    结果: {item}");
        }
        
        Console.WriteLine("\n使用ToList()避免重复执行:");
        var cachedQuery = query.ToList();
        Console.WriteLine("第一次访问缓存:");
        foreach (var item in cachedQuery.Take(3))
        {
            Console.WriteLine($"    结果: {item}");
        }
        
        Console.WriteLine("\n第二次访问缓存:");
        foreach (var item in cachedQuery.Take(3))
        {
            Console.WriteLine($"    结果: {item}");
        }
    }
    
    public static void DemonstrateExecutionTiming()
    {
        Console.WriteLine("\n--- 执行时机对比 ---");
        
        var students = DataGenerator.GenerateStudents();
        
        // 延迟执行的查询
        var deferredQuery = students
            .Where(s =>
            {
                Console.WriteLine($"  过滤学生: {s.Name}");
                return s.GPA >= 3.5;
            })
            .Select(s =>
            {
                Console.WriteLine($"  投影学生: {s.Name}");
                return new { s.Name, s.GPA, Grade = s.GPA >= 3.8 ? "优秀" : "良好" };
            });
        
        Console.WriteLine("延迟查询已创建,但未执行");
        
        Console.WriteLine("\n开始枚举延迟查询:");
        var deferredResults = deferredQuery.Take(3).ToList();
        
        Console.WriteLine("延迟查询结果:");
        foreach (var result in deferredResults)
        {
            Console.WriteLine($"  {result.Name}: {result.GPA:F2} ({result.Grade})");
        }
        
        // 立即执行的查询
        Console.WriteLine("\n--- 立即执行查询 ---");
        var immediateResults = students
            .Where(s =>
            {
                Console.WriteLine($"  过滤学生: {s.Name}");
                return s.GPA >= 3.5;
            })
            .Select(s =>
            {
                Console.WriteLine($"  投影学生: {s.Name}");
                return new { s.Name, s.GPA, Grade = s.GPA >= 3.8 ? "优秀" : "良好" };
            })
            .ToList(); // 立即执行
        
        Console.WriteLine("\n立即执行结果:");
        foreach (var result in immediateResults.Take(3))
        {
            Console.WriteLine($"  {result.Name}: {result.GPA:F2} ({result.Grade})");
        }
    }
}

5. 总结

本章深入介绍了C# LINQ的核心概念:

  1. LINQ基础:概念、查询语法vs方法语法
  2. 标准查询操作符:过滤、投影、排序、分组
  3. 聚合操作符:数值聚合、集合操作
  4. 执行模式:延迟执行vs立即执行

关键要点: - LINQ提供统一的查询语法 - 查询语法和方法语法可以互换使用 - 大多数LINQ操作符支持延迟执行 - 合理使用ToList()、ToArray()等方法控制执行时机 - 注意延迟执行可能导致的重复计算问题 - 选择合适的操作符提高查询性能

下一章预告: 下一章我们将学习文件I/O和序列化,包括文件读写、流操作、JSON/XML序列化等内容。