C# LINQ 延迟执行
最后修改于 2025 年 5 月 14 日
本教程探讨 C# 中的 LINQ 延迟执行,这是一个控制查询何时评估的关键概念。 您将学习延迟执行的工作原理、其优势以及如何有效地管理查询执行。
延迟执行,也称为惰性评估,将 LINQ 查询执行延迟到需要结果时。 这种方法允许灵活的查询组合、优化和高效的资源使用,尤其是在处理大型数据集或数据库时。
延迟执行的主要优势
- 查询组合: 以多个步骤构建复杂的查询,而无需立即执行。
- 性能优化: 仅在必要时执行查询,从而减少不必要的计算。
- 动态数据源: 反映数据源中的更改,直到查询被枚举。
但是,延迟执行需要仔细处理,因为每次枚举查询时都会重新评估查询,如果管理不当,可能会导致意外结果或性能问题。
基本示例
一个简单的示例演示了延迟执行如何将查询评估延迟到枚举时。
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 方法分为使用延迟执行和触发立即执行的方法。
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}");
像 Where 和 Select 这样的方法会延迟执行,而 Count、ToList 和 ToArray 会强制立即执行。
$ dotnet run Deferred query results: banana cherry elderberry Immediate count result: 2
多次枚举注意事项
每次枚举延迟查询时都会重新评估它们,如果数据源或逻辑发生变化,可能会导致不同的结果。
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
强制立即执行
使用像 ToList 或 ToArray 这样的转换方法来强制立即查询执行并缓存结果。
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 方法链保持延迟执行,直到最终枚举,从而允许复杂的查询组合。
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
嵌套集合的延迟执行
延迟执行对于处理嵌套集合非常有用,例如动态过滤内部集合。
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
数据库查询中的延迟执行
延迟执行对于数据库查询至关重要,可以最大限度地减少往返数据库的次数。
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 时才发送到数据库。
延迟执行的最佳实践
为了有效地使用延迟执行
- 避免意外的副作用: 确保查询逻辑是确定性的,以防止在多次枚举时产生不同的结果。
- 在需要时缓存结果: 如果查询开销很大或需要一致的输出,请使用
ToList或ToArray来存储结果。 - 监控性能: 对可能多次重新评估的复杂查询保持谨慎。
来源
本教程涵盖了 C# 中 LINQ 延迟执行的要点,包括其机制、优势和实际应用。
作者
列出所有 C# 教程。