当你刚开始用C#编程时,return感觉就像是逻辑中很自然的一部分。你计算某些内容,然后将其返回。
但在某些时候,特别是在性能要求高或数据密集型的应用程序中,你会遇到这个不太熟悉的"亲戚":yield return。
你的第一反应可能是:
"既然return已经能完成工作,为什么还要引入新的关键字?"
这个想法很合理——直到你意识到yield不仅仅是关于返回值,而是关于这些值如何以及何时被生成和消费的。
理解return:经典方法
让我们从你已经了解的开始。
当你调用一个使用return的方法时,它会执行内部的所有内容,完成工作,最后给你结果。如果你要返回一个列表,该方法会先在内存中构建整个列表,然后才返回它。
例如,假设你想要1到10之间的所有偶数:
public List<int> GetEvenNumbers()
{
var result = new List<int>();
for (int i = 1; i <= 10; i++)
{
if (i % 2 == 0)
result.Add(i);
}
return result;
}
这个方法在退出之前会构建完整的偶数列表。很简单,对吧?
但想象一下你要获取100万条记录,或者处理10GB的日志文件。突然间,这种"先构建,后返回"的方法就变得缓慢、内存占用高且不灵活。
yield return登场:惰性执行者
这就是yield return登场的时候,它就像一个聪明、懒惰但高效的工人。
yield return不会一次性构建整个结果,而是一次给你一个项目——并等待下一个项目被请求时才继续。
这是相同的偶数示例,但使用yield:
public IEnumerable<int> GetEvenNumbers()
{
for (int i = 1; i <= 10; i++)
{
if (i % 2 == 0)
yield return i;
}
}
这个方法不会一次性执行所有内容。相反,当你在循环中遍历时,它开始一次一个地产生值。
当你在foreach中调用它时:
foreach (var n in GetEvenNumbers())
{
Console.WriteLine(n);
}
循环请求第一个项目。方法运行直到第一个yield return,暂停,返回值,然后下次从暂停的地方恢复。
这就像一次阅读一页书,而不是在评论之前一次性阅读整本书。
实际问题:读取日志文件
假设你的应用程序必须读取一个有数百万行的日志文件。如果你使用一个将所有内容读入内存的方法,你会面临速度慢、内存不足崩溃或加载时间长的问题。
public List<string> ReadLogs()
{
var allLines = new List<string>();
foreach (var line in File.ReadAllLines("logs.txt"))
allLines.Add(line);
return allLines;
}
这行代码一次性读取所有内容。
现在与以下代码比较:
public IEnumerable<string> ReadLogs()
{
foreach (var line in File.ReadLines("logs.txt"))
yield return line;
}
美妙之处?
你现在可以一次处理一行——立即开始打印、过滤或响应,而无需等待整个文件加载完成。
假设你正在构建一个流式传输数据的API
假设你的API向前端发送10,000条用户记录。如果你先创建整个列表然后返回,你的用户需要等待直到所有内容都准备好。这会产生延迟。
使用yield,你可以将数据——一次一个项目——流式传输到客户端。你的API变得响应更快、更快速且内存友好。
无限序列?yield是唯一选择
如果你需要无限生成斐波那契数列怎么办?
使用普通的return,这是不可能的,因为你必须知道要生成多少数据。
使用yield,你可以编写如下代码:
public IEnumerable<int> GetFibonacci()
{
int a = 0, b = 1;
while (true)
{
yield return a;
int temp = a;
a = b;
b = temp + b;
}
}
它永远不会完成运行,但这就是重点——你可以在方法外部控制需要多少:
foreach (var num in GetFibonacci().Take(10))
Console.WriteLine(num);
幕后到底发生了什么?
当你使用yield return时,C#不会像普通代码那样编译它。相反,它创建一个状态机——一个带有MoveNext()方法的特殊类,它:
-
• 跟踪你离开的位置 -
• 存储局部变量 -
• 从最后一个yield return点恢复
这实现了暂停和恢复行为——这是return无法做到的。
何时使用yield return
在以下情况下使用它:
-
• 你正在处理大型数据集 -
• 你想要避免创建中间集合 -
• 你需要在值可用时立即返回 -
• 你正在构建实时数据管道 -
• 你正在处理可能无限继续的数据(如监控日志、传感器或API分页)
何时不使用yield return
在以下情况下避免使用:
-
• 你需要随机访问数据(result[5]) -
• 你需要重复缓存或重用结果 -
• 在使用之前需要操作整个结果集
在这些情况下,最好使用传统的List
最终要点
如果return像是一次性的呼喊——快速、响亮、最终——
那么yield return就像是一场对话,数据只在需要时逐步到来。
你并不总是需要携带整桶水。
有时候,一次传递一杯水更聪明——这就是yield成为你最好朋友的地方。

