csharp:linq

这是本文档旧的修订版!


C# LINQ (Language Integrated Query) 详解

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

它的核心优势在于:

  • 一致性:无论数据源是 List 还是 SQL 数据库,查询语法基本一致。
  • 强类型:编译时检查错误,而不是运行时才发现 SQL 拼写错误。
  • 可读性:代码更简洁,意图更明确。

为了演示,我们先定义一个简单的 `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" }
};

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

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

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

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

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

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

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

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

// 只获取所有学生的姓名 (返回 List<string>)
var names = students.Select(s => s.Name);
 
// 构造匿名对象 (只包含 ID 和 姓名)
var simpleList = students.Select(s => new { s.Id, s.Name });
// 按年龄升序排序
var sortedByAge = students.OrderBy(s => s.Age);
 
// 按年龄降序,如果年龄相同则按名字升序
var sortedComplex = students.OrderByDescending(s => s.Age)
                            .ThenBy(s => s.Name);

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

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

用于计算统计数据。

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);    // 年龄总和

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

// 获取第一个元素,如果没有则抛出异常
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);

将数据按某个键归类。

// 按班级分组
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}");
    }
}
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(); // 去重

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

  • 延迟执行 (Deferred Execution):
    • 大多数 LINQ 操作符(如 `Where`, `Select`, `OrderBy`)不会立即查询数据
    • 它们只是构建了一个查询计划。
    • 只有当你遍历结果(`foreach`)时,查询才会真正执行。
  • 立即执行 (Immediate Execution):
    • 某些方法会强制立即执行查询并把结果存入内存。
    • 常见方法:`ToList()`, `ToArray()`, `Count()`, `First()`.
// 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()` 缓存结果,否则每次遍历都会重新执行一遍查询逻辑(如果连的是数据库,这会造成性能浪费)。
操作类型 常用方法
:— :—
筛选 Where, OfType
投影 Select, SelectMany
排序 OrderBy, OrderByDescending, ThenBy
聚合 Count, Sum, Min, Max, Average
量词 Any, All, Contains
分区 Skip, Take
转换 ToList, ToArray, ToDictionary

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • csharp/linq.1764121639.txt.gz
  • 最后更改: 2025/11/26 09:47
  • 张叶安