📋 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 暂停点之间"存活"的局部变量被提升为状态机类的字段,包括:

延迟执行 · 执行时机

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>
消费语法foreachawait foreach
取下一个MoveNext()MoveNextAsync()
方法声明public ...()public async ...()
暂停/恢复yield return / MoveNextyield return + await / MoveNextAsync

关键限制

限制说明
不能用 out/ref 参数迭代器方法参数不能是 ref/out——状态机无法安全持有它们的引用
yield return 不能在 try-catch可以在 try-finally 中(C# 编译器保证 finally 在 Dispose 时执行),但不能在 catch 块中
不能在匿名方法/lambda 中yield return 只能出现在普通方法、运算符重载、或属性/索引器的 get 访问器中
不能在 unsafe 代码块中不安全代码中不可用