yield return 是 C# 中用于定义迭代器方法的关键字,它不终止方法而是暂停执行并返回一个值,由编译器生成状态机实现 IEnumerable;必须返回 IEnumerable 等类型,不可混用普通 return,且受 try/catch 和闭包陷阱等限制。
yield return 不是普通函数的 return,它不会终止方法执行,而是暂停当前迭代器状态,把值“交出去”,等下一次调用 MoveNext() 时从暂停处继续。这意味着方法体实际被编译成一个实现了 IEnumerable 和 IEnumerator 的状态机类,你写的代码只是语法糖。
常见误解是把它当“逐个 return”,结果在循环里写了 return 混用,导致后续 yield return 永远不执行 —— 这种写法直接报错或逻辑中断。
IEnumerable、IEnumerable、IAsyncEnumerable(C# 8+)之一return 语句(除了 return; 用于提前退出迭代)try 块中有 yield return,除非 catch 和 finally 中没有 yield return(编译器限制)最典型场景:把一个计算过程或数据流封装成可枚举对象,避免一次性加载全部数据到内存。
public static IEnumerableGetEvenNumbers(int max) { for (int i = 0; i <= max; i += 2) { yield return i; } }
调用时:
foreach (int n in GetEvenNumbers(10))
{
Console.WriteLine(n); // 输出 0, 2, 4, 6, 8, 10
}注意:GetEvenNumbers(10) 调用本身不执行循环,只返回一个未启动的迭代器;真正执行从 foreach 第一次调用 MoveNext() 开始。
yield return 后,方法暂停,局部变量(如 i)状态被保留yield break;,它相当于“迭代结束”,不是异常yield return 后写任何代码(除非是 yield break; 或空语句),编译器会报错对比两种实现方式:
// ❌ 先构造完整列表再返回 public static ListGetEvenNumbersList(int max) { var list = new List (); for (int i = 0; i <= max; i += 2) { list.Add(i); } return list; }
// ✅ yield return 流式生成 public static IEnumerableGetEvenNumbers(int max) { for (int i = 0; i <= max; i += 2) { yield return i; } }
关键区别:
List 版本必须分配足够内存容纳所有元素(比如 max = 1000000 就要存 50 万整数),且全部算完才返回yield return 版本按需计算,内存占用恒定(仅保存当前状态),适合大数据流、IO 边界(如逐行读文件)、或消费者可能提前退出的场景(如 .FirstOrDefault()).ToList()),因为每次调用都新建迭代器下面这段代码很常见,但结果不符合直觉:
public static IEnumerable> GetDelegates() { var actions = new List >(); for (int i = 0; i < 3; i++) { yield return () => i; // ❌ 所有委托都返回 3 } }
原因:所有 yield return 返回的 lambda 共享同一个变量 i,而迭代器直到遍历时才执行,此时循环早已结

i == 3。
修复方式:在循环内创建局部副本:
for (int i = 0; i < 3; i++)
{
int localI = i; // ✅ 每次迭代独立副本
yield return () => localI;
}另一个坑是误以为 yield return 方法“立即执行”——它其实完全惰性。如果你在 yield return 方法里打开文件、数据库连接或 HTTP 请求,这些资源会在第一次 MoveNext() 时才初始化,且若没正确释放(比如没用 using 包裹),容易造成资源泄漏或并发问题。