ZetCode

C# LINQ 延迟执行

最后修改于 2025 年 5 月 14 日

本教程探讨 C# 中的 LINQ 延迟执行,这是一个控制查询何时评估的关键概念。 您将学习延迟执行的工作原理、其优势以及如何有效地管理查询执行。

延迟执行,也称为惰性评估,将 LINQ 查询执行延迟到需要结果时。 这种方法允许灵活的查询组合、优化和高效的资源使用,尤其是在处理大型数据集或数据库时。

延迟执行的主要优势

但是,延迟执行需要仔细处理,因为每次枚举查询时都会重新评估查询,如果管理不当,可能会导致意外结果或性能问题。

基本示例

一个简单的示例演示了延迟执行如何将查询评估延迟到枚举时。

Program.cs
List<int> numbers = [1, 2, 3, 4, 5];

// Query definition - not executed yet
var query = from n in numbers
            where n % 2 == 0
            select n;

Console.WriteLine("Query created");

// Modify the data source
numbers.Add(6);
numbers.Add(7);
numbers.Add(8);

// Query execution happens here
Console.WriteLine("Query results:");
foreach (var num in query)
{
    Console.WriteLine(num);
}

查询已定义,但直到 foreach 循环才执行。 在枚举之前对 numbers 的修改会影响结果。

var query = from n in numbers
            where n % 2 == 0
            select n;

此 LINQ 查询创建一个 IQueryable 对象,该对象表示查询逻辑,但不会立即执行它。

$ dotnet run
Query created
Query results:
2
4
6
8

延迟执行与立即执行

LINQ 方法分为使用延迟执行和触发立即执行的方法。

Program.cs
List<string> fruits = ["apple", "banana", "cherry", "date"];

// Deferred execution
var deferredQuery = fruits.Where(f => f.Length > 5);

// Immediate execution
var immediateResult = fruits.Count(f => f.Length > 5);

fruits.Add("elderberry");
fruits.Add("fig");

Console.WriteLine("Deferred query results:");
foreach (var fruit in deferredQuery)
{
    Console.WriteLine(fruit);
}

Console.WriteLine($"\nImmediate count result: {immediateResult}");

WhereSelect 这样的方法会延迟执行,而 CountToListToArray 会强制立即执行。

$ dotnet run
Deferred query results:
banana
cherry
elderberry

Immediate count result: 2

多次枚举注意事项

每次枚举延迟查询时都会重新评估它们,如果数据源或逻辑发生变化,可能会导致不同的结果。

Program.cs
Random random = new();
var numbers = Enumerable.Range(1, 5).Select(n => random.Next(1, 100));

var query = numbers.Where(n => n % 2 == 0);

Console.WriteLine("First enumeration:");
foreach (var num in query) Console.WriteLine(num);

Console.WriteLine("\nSecond enumeration:");
foreach (var num in query) Console.WriteLine(num);

每次枚举都会生成新的随机数,从而产生不同的结果。

$ dotnet run
First enumeration:
42
88

Second enumeration:
56
24

强制立即执行

使用像 ToListToArray 这样的转换方法来强制立即查询执行并缓存结果。

Program.cs
List<int> data = [10, 20, 30, 40, 50];

// Deferred execution
var deferredQuery = data.Select(n => {
    Console.WriteLine($"Processing {n}");
    return n * 2;
});

// Force immediate execution
var immediateList = deferredQuery.ToList();

Console.WriteLine("\nResults:");
foreach (var num in immediateList)
{
    Console.WriteLine(num);
}

ToList 立即执行查询,随后的枚举使用缓存的结果。

$ dotnet run
Processing 10
Processing 20
Processing 30
Processing 40
Processing 50


Results:
20
40
60
80
100

链式调用和延迟执行

LINQ 方法链保持延迟执行,直到最终枚举,从而允许复杂的查询组合。

Program.cs
List<string> words = ["sky", "blue", "cloud", "forest", "ocean"];

var query = words
    .Where(w => w.Length > 3)
    .OrderBy(w => w)
    .Select(w => w.ToUpper());

words.Add("river");

foreach (var word in query)
{
    Console.WriteLine(word);
}

整个链仅在枚举期间进行评估,包括新添加的 "river"。

$ dotnet run
BLUE
CLOUD
FOREST
OCEAN
RIVER

嵌套集合的延迟执行

延迟执行对于处理嵌套集合非常有用,例如动态过滤内部集合。

Program.cs
var departments = new[]
{
    new { Name = "HR", Employees = new[] { "Alice", "Bob" } },
    new { Name = "IT", Employees = new[] { "Charlie", "Dave" } }
};

var query = from dept in departments
            let activeEmployees = dept.Employees.Where(e => e.Length > 4)
            where activeEmployees.Any()
            select new { dept.Name, ActiveEmployees = activeEmployees };

foreach (var dept in query)
{
    Console.WriteLine($"{dept.Name}: {string.Join(", ", dept.ActiveEmployees)}");
}

仅当枚举查询时才过滤内部集合,从而反映最新数据。

$ dotnet run
HR: Alice
IT: Charlie

数据库查询中的延迟执行

延迟执行对于数据库查询至关重要,可以最大限度地减少往返数据库的次数。

Program.cs
var dbQuery = dbContext.Products
    .Where(p => p.Price > 100)
    .OrderBy(p => p.Name);

// Additional filtering can be added later
if (categoryFilter != null)
{
    dbQuery = dbQuery.Where(p => p.Category == categoryFilter);
}

// Query executes only when materialized
var results = dbQuery.ToList();

查询是增量构建的,仅当调用 ToList 时才发送到数据库。

延迟执行的最佳实践

为了有效地使用延迟执行

来源

LINQ 中的延迟执行与惰性评估

本教程涵盖了 C# 中 LINQ 延迟执行的要点,包括其机制、优势和实际应用。

作者

我的名字是 Jan Bodnar,我是一位充满热情的程序员,拥有丰富的编程经验。 我从 2007 年开始撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 C# 教程