📋 yield return 同步迭代器 · 速查表
C# 2.0+ · 编译器状态机变换 · 延迟执行模式
基本语法
最简单的迭代器
public IEnumerable<string> GetItems()
{
yield return "one";
yield return "two";
yield return "three";
// 方法结束 = 隐式 yield break
}
核心接口
| 接口 | 职责 | 关键成员 |
IEnumerable<T> | 可枚举对象——"我有一批数据" | GetEnumerator() |
IEnumerator<T> | 枚举器——"我指向当前那条数据" | Current + MoveNext() |
yield 关键字
| 关键字 | 作用 | 类比 |
yield return value | 产出一条数据,暂停方法,等下次 MoveNext | 协程的 yield |
yield break | 终止迭代,状态机标记为结束 | 普通方法的 return |
状态机结构(编译器生成)
你写的代码: 编译器生成的 MoveNext():
IEnumerable<int> F() bool MoveNext() {
{ switch (__state) {
// 语句块 A case 0: goto A_resume;
yield return 10; case 1: goto B_resume;
// 语句块 B case 2: goto C_resume;
yield return 20; } }
// 语句块 C
}
| __state 值 | 含义 |
-2 | 初始状态(刚创建,还没开始迭代) |
0, 1, 2, ... | 在某个 yield return 之后暂停(按出现顺序编号) |
-1 | 迭代结束(或已 Dispose) |
局部变量→字段
所有在 yield return 暂停点之间"存活"的局部变量被提升为状态机类的字段,包括:
- 显式声明的局部变量
foreach 迭代变量
using 变量(编译器保证 Dispose 在 finally 中调用)
- 方法参数中非 ref/out 的参数
延迟执行 · 执行时机
var seq = GetItems(); // ← 此时方法体没有执行!只创建了状态机对象
foreach (var x in seq) // ← 第一次 MoveNext() 才触发方法体执行
{ // 后续每次 MoveNext() 从上次暂停点继续
Console.WriteLine(x);
}
| 操作 | 是否触发方法体执行 |
| 调用迭代器方法(拿到 IEnumerable) | ❌ 不触发 |
.ToList() / .ToArray() | ✅ 触发(立即遍历全部) |
.Count()(LINQ) | ✅ 触发(遍历全部来计数) |
foreach / MoveNext() | ✅ 触发(一次一条) |
.First() / .Take(n) | ✅ 触发(只遍历到满足条件为止) |
常用模式
过滤 模式一
public static IEnumerable<T> Filter<T>(
IEnumerable<T> source, Func<T, bool> pred)
{
foreach (var item in source)
if (pred(item))
yield return item;
}
无限序列 模式二
public static IEnumerable<long> Fib()
{
long a = 0, b = 1;
while (true) { yield return a; (a, b) = (b, a + b); }
}
// 用 Take() 控制——Fib().Take(10)
提前终止 模式三
public static IEnumerable<T> TakeUntil<T>(
IEnumerable<T> source, Func<T, bool> stopWhen)
{
foreach (var item in source)
{
if (stopWhen(item)) yield break;
yield return item;
}
}
资源安全释放 模式四
public IEnumerable<string> ReadLines(string path)
{
using var reader = new StreamReader(path);
string line;
while ((line = reader.ReadLine()) != null)
yield return line;
// reader.Dispose() 在 finally 中自动调用
}
何时使用 / 何时不用
| 场景 | 建议 |
| 大数据量逐条处理 | ✅ yield return |
| 链式数据转换管道 | ✅ yield return |
| 无限序列 / 数学序列 | ✅ yield return |
| 小数据量(几十条) | ⚠️ 随意 |
| 需要随机访问(索引) | ❌ List/Array |
| 需要排序 / 分组 | ❌ List/Array |
| 方法里没有循环/迭代 | ❌ 直接返回即可 |
yield return vs 替代方案
| yield return | 返回 List<T> |
| 内存 | O(1) — 当前元素 | O(n) — 全部元素 |
| 首条延迟 | 几乎为零 | 等于处理全部数据的耗时 |
| 支持提前终止 | ✅ — foreach break 即停 | ❌ — 数据已经全在内存里 |
| 支持无限序列 | ✅ | ❌ |
| 封装性 | ✅ — 调用方拿不到内部集合 | ⚠️ — 可 cast 回 List 修改 |
同步 vs 异步迭代器对比
| 同步 (C# 2.0+) | 异步 (C# 8+) |
| 返回类型 | IEnumerable<T> | IAsyncEnumerable<T> |
| 消费语法 | foreach | await foreach |
| 取下一个 | MoveNext() | MoveNextAsync() |
| 方法声明 | public ...() | public async ...() |
| 暂停/恢复 | yield return / MoveNext | yield return + await / MoveNextAsync |
关键限制
| 限制 | 说明 |
不能用 out/ref 参数 | 迭代器方法参数不能是 ref/out——状态机无法安全持有它们的引用 |
yield return 不能在 try-catch 中 | 可以在 try-finally 中(C# 编译器保证 finally 在 Dispose 时执行),但不能在 catch 块中 |
| 不能在匿名方法/lambda 中 | yield return 只能出现在普通方法、运算符重载、或属性/索引器的 get 访问器中 |
不能在 unsafe 代码块中 | 不安全代码中不可用 |