目录

C# LINQ (Language Integrated Query) 详解

LINQ (语言集成查询) 是 C# 中的一组功能,它允许你使用统一的语法来查询不同的数据源(如内存集合、数据库、XML 等)。

它的核心优势在于:

1. 准备数据 (Data Source)

为了演示,我们先定义一个简单的 `Student` 类和一些测试数据。

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 ClassName { get; set; }
}
 
// 模拟数据源
var students = new List<Student>
{
    new Student { Id = 1, Name = "Alice", Age = 22, ClassName = "A" },
    new Student { Id = 2, Name = "Bob", Age = 19, ClassName = "B" },
    new Student { Id = 3, Name = "Charlie", Age = 24, ClassName = "A" },
    new Student { Id = 4, Name = "David", Age = 20, ClassName = "B" },
    new Student { Id = 5, Name = "Eve", Age = 22, ClassName = "A" }
};

2. 两种语法风格 (Syntax Styles)

LINQ 有两种写法,它们在编译后是等价的。

2.1 查询语法 (Query Syntax)

看起来像 SQL,适合复杂的连接(Join)和分组操作。

// 查找所有年龄大于 20 岁的学生
var query = from s in students
            where s.Age > 20
            select s;

2.2 方法语法 (Method Syntax)

使用 Lambda 表达式,是现代 C# 开发中最常用的方式,支持所有 LINQ 操作符。

// 查找所有年龄大于 20 岁的学生
var query = students.Where(s => s.Age > 20);

3. 常用操作符 (Common Operators)

以下示例主要使用方法语法

3.1 筛选 (Filtering) - Where

// 找出班级是 "A" 的学生
var classAStudents = students.Where(s => s.ClassName == "A");

3.2 投影 (Projection) - Select

用于转换数据,例如只取某一列,或者构造新的对象。

// 只获取所有学生的姓名 (返回 List<string>)
var names = students.Select(s => s.Name);
 
// 构造匿名对象 (只包含 ID 和 姓名)
var simpleList = students.Select(s => new { s.Id, s.Name });

3.3 排序 (Sorting) - OrderBy / ThenBy

// 按年龄升序排序
var sortedByAge = students.OrderBy(s => s.Age);
 
// 按年龄降序,如果年龄相同则按名字升序
var sortedComplex = students.OrderByDescending(s => s.Age)
                            .ThenBy(s => s.Name);

3.4 分页 (Partitioning) - Skip / Take

常用于 Web 开发中的分页功能。

// 跳过前 2 个,取接下来的 2 个 (获取第 3、4 名学生)
var page2 = students.Skip(2).Take(2);

3.5 聚合 (Aggregation)

用于计算统计数据。

int count = students.Count();             // 总数
int maxAge = students.Max(s => s.Age);    // 最大年龄
double avgAge = students.Average(s => s.Age); // 平均年龄
int sumAge = students.Sum(s => s.Age);    // 年龄总和

3.6 元素获取 (Element Operators)

获取单个元素,注意处理空值情况。

// 获取第一个元素,如果没有则抛出异常
var first = students.First(s => s.Age > 20);
 
// 获取第一个元素,如果没有则返回 null (推荐)
var firstSafe = students.FirstOrDefault(s => s.Age > 100); 
 
// 检查是否包含满足条件的元素 (返回 bool)
bool hasUnderage = students.Any(s => s.Age < 18);
 
// 检查是否所有元素都满足条件 (返回 bool)
bool allAdults = students.All(s => s.Age >= 18);

4. 高级操作 (Advanced)

4.1 分组 (GroupBy)

将数据按某个键归类。

// 按班级分组
var grouped = students.GroupBy(s => s.ClassName);
 
foreach (var group in grouped)
{
    Console.WriteLine($"班级: {group.Key}"); // Key 是 ClassName
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name}");
    }
}

4.2 集合操作

var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 3, 4, 5 };
 
var union = list1.Union(list2); // 并集: 1, 2, 3, 4, 5
var intersect = list1.Intersect(list2); // 交集: 3
var except = list1.Except(list2); // 差集 (list1 - list2): 1, 2
var distinct = list1.Concat(list1).Distinct(); // 去重

5. 延迟执行 vs 立即执行 (Important!)

这是 LINQ 中最重要的概念之一。

// 1. 定义查询 (此时还没运行)
var query = students.Where(s => s.Age < 20);
 
// 2. 修改数据源 (注意:我们在定义查询之后修改了数据)
students.Add(new Student { Name = "NewKid", Age = 15 });
 
// 3. 执行查询 (foreach 或 ToList)
// 结果会包含 "NewKid",因为查询是在这一行才真正执行的!
var result = query.ToList(); 

最佳实践:

  1. 如果你需要多次遍历同一个查询结果,请先调用 `.ToList()` 缓存结果,否则每次遍历都会重新执行一遍查询逻辑(如果连的是数据库,这会造成性能浪费)。

6. 总结

操作类型 常用方法
筛选 Where, OfType
投影 Select, SelectMany
排序 OrderBy, OrderByDescending, ThenBy
聚合 Count, Sum, Min, Max, Average
量词 Any, All, Contains
分区 Skip, Take
转换 ToList, ToArray, ToDictionary