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.txt
  • 最后更改: 2025/11/26 09:48
  • 张叶安