前置知识: C#

LINQ延迟与立即执行

00:00
2 min Advanced 2026/6/14

C# LINQ延迟执行与立即执行详解。

概述

LINQ执行方式分为延迟和立即执行两种。延迟枚举时才计算结果,适合链式操作和查询组合;立即执行在调用时立即计算并返回结果。理解两者的区别是避免 Bug 和优化性能的关键。

基础概念

延迟执行 vs 立即执行

特性延迟立即执行
执行时枚举时(如 foreach)调用时立即执行
返回类型IEnumerable具体集合
枚举每次重新计算结果缓存
适用操作Where, Select, OrderByToList, Count, First

快速上手

延迟执行

// 延迟执行:定义查询时不执行,枚举时才执行
var query = numbers.Where(n => n > 3);  // 不执行
var results = query.ToList();           // 此时才执行

// 延迟执行的方法
// Where, Select, OrderBy, GroupBy, Skip, Take,
// Concat, Union, SelectMany, Reverse, ...

立即执行

// 立即执行:调用时立即计算结果
var count = numbers.Count();            // 立即执行
var list = numbers.ToList();            // 立即执行
var first = numbers.First();            // 立即执行
var any = numbers.Any(n => n > 5);      // 立即执行
var dict = numbers.ToDictionary(n => n); // 立即执行

// 立即执行的方法
// ToList, ToArray, ToDictionary, ToLookup,
// Count, LongCount, First, FirstOrDefault,
// Last, Single, Any, All, Aggregate, ...

详细用法

延迟执行的陷阱

// 陷阱一:修改源数据后查询结果变化
var numbers = new List<int> { 1, 2, 3 };
var query = numbers.Where(n => n > 1);
numbers.Add(4);  // 修改源数据
query.Count();   // 3(包含新添加的 4)

// 解决:使用 ToList() 冻结结果
var frozen = numbers.Where(n => n > 1).ToList();
numbers.Add(5);
frozen.Count();  // 仍然是之前的结果

// 陷阱二:闭包捕获变量
var filters = new List<Func<int, bool>>();
for (int i = 0; i < 3; i++) {
    filters.Add(n => n > i); // 所有过滤器都捕获同一个 i
}
// 循环结束后 i = 3,所有过滤器都是 n > 3

// 解决:使用局部变量
for (int i = 0; i < 3; i++) {
    int threshold = i; // 每次循环创建新的局部变量
    filters.Add(n => n > threshold);
}

多次枚举问题

// 问题:延迟查询被多次枚举,每次都重新计算
var query = expensiveDataSource.Where(Filter).Select(Transform);

// 第一次枚举:执行完整查询
foreach (var item in query) { Process(item); }

// 第二次枚举:再次执行完整查询(浪费!)
var count = query.Count();

// 解决:使用 ToList() 缓存结果
var cached = query.ToList();
foreach (var item in cached) { Process(item); }
var count = cached.Count;

IQueryable vs IEnumerable

// IEnumerable:客户端执行,在内存中过滤
IEnumerable<User> users = dbContext.Users.AsEnumerable();
var result = users.Where(u => u.Age > 18); // 全部加载到内存再过滤

// IQueryable:服务端执行,翻译为 SQL
IQueryable<User> users = dbContext.Users;
var result = users.Where(u => u.Age > 18); // 翻译为 WHERE Age > 18

// 对比
| 类型        | 执行位置         | 适用场景   |
| ----------- | ---------------- | ---------- |
| IEnumerable | 客户端(内存)   | 内存集合   |
| IQueryable  | 服务端(数据库) | 数据库查询 |

// 常见错误:过早转为 IEnumerable
var users = dbContext.Users
    .AsEnumerable()  // 错误!后续操作在内存中执行
    .Where(u => u.Age > 18); // 应该在数据库过滤

常见场景

动态查询构建

// 利用延迟执行构建动态查询
IQueryable<User> query = dbContext.Users;

if (!string.IsNullOrEmpty(nameFilter))
    query = query.Where(u => u.Name.Contains(nameFilter));

if (minAge.HasValue)
    query = query.Where(u => u.Age >= minAge.Value);

if (cityFilter != null)
    query = query.Where(u => u.City == cityFilter);

// 所有条件组合完成后,一次执行
var results = await query.ToListAsync();

流式处理大数据

// 利用延迟执行处理大数据集,避免一次性加载
var largeData = File.ReadLines("large.csv") // 延迟读取
    .Select(ParseLine)
    .Where(item => item.IsValid)
    .GroupBy(item => item.Category)
    .Select(g => new { Category = g.Key, Count = g.Count() });

// 枚举时逐行处理,内存占用低
foreach (var group in largeData) {
    Console.WriteLine($"{group.Category}: {group.Count}");
}

注意事项

  • 延迟查询每次枚举都会重新执行,需要缓存时使用 ToList/ToArray
  • 修改数据会影响延迟查询结果注意时序
  • IQueryable 的链式调用数据库执行,不要过早转为 IEnumerable
  • 闭包捕获变量时注意延迟执行影响
  • 立即执行方法(如 First)在空序列上会抛出异常,使用 FirstOrDefault 更安全
  • Any() 比 Count() > 0 更效,因为 Any 找到第一个匹配返回

进阶用法

自定义延迟操作符

// 使用 yield return 实现自定义延迟操作符
public static IEnumerable<T> WhereNot<T>(
    this IEnumerable<T> source, Func<T, bool> predicate) {
    foreach (var item in source) {
        if (!predicate(item))
            yield return item; // 延迟返回
    }
}

// 使用
var result = numbers.WhereNot(n => n % 2 == 0); // 过滤偶数

异步 LINQ

// 使用 System.Linq.Async 进行异步 LINQ 操作
var results = await asyncEnumerable
    .WhereAwait(async item => await FilterAsync(item))
    .SelectAwait(async item => await TransformAsync(item))
    .ToListAsync();

// IAsyncEnumerable 延迟执行
async IAsyncEnumerable<int> GenerateNumbersAsync() {
    for (int i = 0; i < 100; i++) {
        await Task.Delay(100);
        yield return i; // 延迟产生
    }
}

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式