C# --- yield关键字 和 Lazy Execution
C# --- yield关键字 和 Lazy Execution
- 延迟执行(Lazy Execution)
- yield关键字
- lazy execution与yield的关系
- LINQ 和 lazy exectuion
延迟执行(Lazy Execution)
- 延迟执行指操作不会立即计算结果,而是在实际需要数据时才执行计算。这种方式避免一次性加载所有数据,节省内存并提升性能。
- 典型应用:LINQ查询、foreach迭代。
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var query = numbers.Where(n => {
Console.WriteLine($"过滤 {n}"); // 副作用代码,用于观察执行时机
return n % 2 == 0;
});
Console.WriteLine("查询已定义,但尚未执行过滤");
foreach (var num in query) // 实际执行过滤操作
{
Console.WriteLine($"结果: {num}");
}
output:
查询已定义,但尚未执行过滤
过滤 1
过滤 2
结果: 2
过滤 3
过滤 4
结果: 4
过滤 5
- Where方法返回的是
IEnumerable<T>
,但此时不会执行过滤逻辑。- 实际过滤发生在foreach遍历时,逐个元素处理
- lazy execution的实现原理用到了
yield
关键字
yield关键字
- 简化迭代器实现:通过yield return按需生成值,
自动生成IEnumerable或IEnumerator。
- 启用延迟执行:迭代器方法在遍历时逐步执行,而非一次性运行完毕。
yield return
:生成一个值,并暂停方法执行,直到下次迭代。yield break
:终止迭代。
示例:生成斐波那契数列
using System;
using System.Collections.Generic;
public class Program
{
public static IEnumerable<int> GenerateFibonacci(int count)
{
int a = 0, b = 1;
for (int i = 0; i < count; i++)
{
Console.WriteLine($"生成第 {i+1} 项");
yield return a; // 暂停并返回值
int temp = a;
a = b;
b = temp + b;
}
}
public static void Main()
{
var fibSequence = GenerateFibonacci(5); // 此时不会执行方法体, 只返回迭代器
Console.WriteLine("开始遍历");
foreach (var num in fibSequence)
{
// 每次循环触发一次迭代,执行方法体里的代码
Console.WriteLine($"结果: {num}");
}
}
}
output:
开始遍历
生成第 1 项
结果: 0
生成第 2 项
结果: 1
生成第 3 项
结果: 1
生成第 4 项
结果: 2
生成第 5 项
结果: 3
- 调用GenerateFibonacci(5)时,方法体不会立即执行。
- 每次foreach循环触发一次迭代,执行到下一个yield return后暂停
lazy execution与yield的关系
核心机制
- yield关键字生成的迭代器方法会自动实现延迟执行。
迭代器方法被调用时,返回一个IEnumerable对象,但方法体代码不会立即运行
。代码的实际执行由foreach或MoveNext()触发,按需生成值
立即执行(Eager Evaluation)
public List<int> GetNumbersEager()
{
List<int> numbers = new List<int>();
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"立即生成: {i}");
numbers.Add(i);
}
return numbers; // 所有数据已生成
}
// 调用时立即生成所有元素:
var list = GetNumbersEager(); // 输出所有"立即生成: x"
foreach (var num in list) { /* 直接遍历已生成的列表 */ }
延迟执行(使用yield)
public IEnumerable<int> GetNumbersLazy()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"延迟生成: {i}");
yield return i; // 按需生成
}
}
// 调用时仅定义迭代器:
var sequence = GetNumbersLazy(); // 无输出
foreach (var num in sequence)
{
// 每次循环触发一次生成
}
延迟生成: 0
延迟生成: 1
延迟生成: 2
...
何时选择延迟执行?
- 处理大数据集:避免一次性加载所有数据到内存。
- 生成无限序列:如斐波那契数列、实时数据流。
- 复杂计算:按需计算,减少不必要的开销。
经典案例
// 生成无限随机数序列
public static IEnumerable<int> InfiniteRandomNumbers()
{
Random rand = new Random();
while (true)
{
yield return rand.Next();
}
}
// 仅取前3个随机数
foreach (var num in InfiniteRandomNumbers().Take(3))
{
Console.WriteLine(num);
}
LINQ 和 lazy exectuion
- 在 C# 中,延迟执行(Lazy Execution) 是 LINQ(Language Integrated Query)的核心特性之一。它允许 LINQ 查询在定义时不立即执行,而是推迟到实际需要数据时才进行计算。这种机制显著优化了性能,尤其是在处理大数据集或链式操作时。
- 延迟执行:LINQ 查询(如 Where、Select、OrderBy)默认返回 IEnumerable 或 IQueryable,这些查询不会立即执行,而是在以下时机触发计算:
- 遍历结果(如 foreach 循环)。
- 调用强制立即执行的方法(如 ToList()、ToArray()、Count())。
- 底层实现:LINQ 方法通过 yield return 和迭代器模式实现延迟执行,按需生成数据
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 定义查询(未执行)
var query = numbers
.Where(n => {
Console.WriteLine($"过滤 {n}");
return n % 2 == 0;
})
.Select(n => {
Console.WriteLine($"映射 {n}");
return n * 10;
});
Console.WriteLine("查询已定义,尚未执行");
numbers.Add(6); // 修改原集合
// 触发执行
foreach (var num in query)
{
Console.WriteLine($"结果: {num}");
}
查询已定义,尚未执行
过滤 1
过滤 2
映射 2
结果: 20
过滤 3
过滤 4
映射 4
结果: 40
过滤 5
过滤 6
映射 6
结果: 60