迭代器 yield
目录
关于 yield 的定义,语法,常见使用场景,以及一个在wpf中使用的demo。
一、定义
yield 是 C# 中用于创建迭代器的关键字。它允许你按需(懒加载)逐个生成序列中的元素,而不是一次性将所有元素存储在内存中。
编译器会将 yield 方法自动转换为状态机类。
┌─────────────────────────────────────────────────────────────┐
│ yield 工作原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 调用方 迭代器方法 │
│ │ │ │
│ │ ──── MoveNext() ───────────>│ │
│ │ │ 执行到 yield return │
│ │ <─── 返回值 + 暂停 ─────────│ │
│ │ │ (保存状态) │
│ │ ──── MoveNext() ───────────>│ │
│ │ │ 从暂停处继续 │
│ │ <─── 返回值 + 暂停 ─────────│ │
│ │ │ │
│ │ ──── MoveNext() ───────────>│ │
│ │ │ yield break / 结束 │
│ │ <─── false ─────────────────│ │
│ │
└─────────────────────────────────────────────────────────────┘二、语法
两种形式:
// 1. yield return - 返回一个元素并暂停
yield return value;
// 2. yield break - 终止迭代
yield break;返回类型要求:
// 必须返回以下类型之一:
IEnumerable<T>
IEnumerable
IEnumerator<T>
IEnumerator基本语法示例:
// 基本用法
public IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
// 使用循环
public IEnumerable<int> GetRange(int start, int end)
{
for (int i = start; i <= end; i++)
{
yield return i;
}
}
// 使用条件和 yield break
public IEnumerable<int> GetUntilNegative(int[] numbers)
{
foreach (var num in numbers)
{
if (num < 0)
yield break; // 遇到负数时终止
yield return num;
}
}三、常见使用场景
1.延迟执行(Lazy Evaluation)
// 只有在实际遍历时才会执行计算
public IEnumerable<int> ExpensiveCalculations()
{
for (int i = 0; i < 1000000; i++)
{
// 只有当需要这个值时才进行计算
yield return ComplexCalculation(i);
}
}
// 如果只取前10个,只会计算10次
var first10 = ExpensiveCalculations().Take(10);2.无限序列
// 生成无限斐波那契数列
public IEnumerable<long> Fibonacci()
{
long a = 0, b = 1;
while (true) // 无限循环
{
yield return a;
(a, b) = (b, a + b);
}
}
// 使用时取需要的数量
var first20 = Fibonacci().Take(20);3.流式处理大文件
// 逐行读取大文件,不会一次加载到内存
public IEnumerable<string> ReadLargeFile(string path)
{
using var reader = new StreamReader(path);
while(!reader.EndOfStream)
{
yeild return reader.ReadLine();
}
}4.树/图的遍历
public IEnumerable<TreeNode> TraversePreOrder(TreeNode root)
{
if (root == null)
{
yield break
}
yield return root;
foreach(var child in root.children)
{
foreach(var node in TraversePreOrder(child))
{
yield return node;
}
}
}5.数据过滤/转换通道
public IEnumerable<T> Where<T>(IEnumerable<T> source, Func<T, bool> predicate)
{
foreach(var item in source)
{
if (predicate(item))
{
yield return item;
}
}
}
public IEnumerable<TResult> Select<T, TResult>(IEnumerable<T> source,Func<T, TResult> selector)
{
foreach(var item in source)
{
yield return selector(item);
}
}四、使用注意事项
- 延迟执行:迭代器方法在调用时不会立即执行,只有在遍历集合时执行。
- 每次遍历重新执行:每次 foreach 都会重新从头执行迭代器方法。
- try-catch限制:不能在 try_catch 块中使用 yield return。
- 匿名方法限制:不能在 lambda 或匿名方法中使用 yield。
- 异常延迟:参数验证等异常会延迟到遍历时才才抛出。
示例1:延迟执行陷阱
错误示例:
public IEnumerable<int> GetData(string param)
{
if (param == null)
{
// 延迟到遍历时才抛出
throw new ArgumentNullException(nameof(param));
}
yield return 1;
yield return 2;
}正确示例:分离验证逻辑
public IEnumerable<int> GetData(string param)
{
if (param == null)
{
// 立即抛出
throw new ArgumentNullException(nameof(param));
}
return GetDataIterator(param);
}
public IEnumerable<int> GetDataIterator(string param)
{
yield return 1;
yield return 2;
}示例2:try-catch限制
// 会引起编译错误:不能在 try-catch 中使用
public IEnumerable<int> Invalid()
{
try
{
yield return 1;
}
catch
{
}
}
// 可以在 try-finally 中使用
public IEnumerable<int> Valid()
{
try
{
yield return 1;
}
finally
{
}
}完整Demo:
<Window
x:Class="demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:demo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="900"
Height="700"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="5" />
<Setter Property="Padding" Value="15,8" />
<Setter Property="FontSize" Value="13" />
</Style>
</Window.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Margin="0,0,0,10"
FontSize="24"
FontWeight="Bold"
Text="Yield Demo" />
<WrapPanel Grid.Row="1" Margin="0,0,0,10">
<Button Click="Fibonacci_Click" Content="📊 斐波那契数列" />
<Button Click="Primes_Click" Content="🔢 质数生成" />
<Button Click="LazyExecution_Click" Content="⏱️ 延迟执行演示" />
<Button Click="YieldBreak_Click" Content="🛑 yield break 演示" />
<Button Click="CustomLinq_Click" Content="🔍 自定义LINQ" />
<Button Click="TreeTraversal_Click" Content="🌳 树遍历" />
<Button Click="PagedData_Click" Content="📄 分页数据" />
<Button
Background="#FFDDDD"
Click="Clear_Click"
Content="🧹 清空" />
<StackPanel
Margin="20,0,0,0"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="数量:" />
<TextBox
x:Name="txtCount"
Width="60"
Padding="5,3"
VerticalAlignment="Center"
Text="10" />
</StackPanel>
</WrapPanel>
<TextBox
x:Name="txtOutput"
Grid.Row="2"
Padding="10"
AcceptsReturn="True"
Background="#1E1E1E"
FontFamily="Consolas"
Foreground="#DCDCDC"
HorizontalScrollBarVisibility="Auto"
IsReadOnly="True"
VerticalScrollBarVisibility="Auto" />
<StatusBar Grid.Row="3" Margin="0,10,0,0">
<StatusBarItem>
<TextBlock x:Name="txtStatus" Text="就绪" />
</StatusBarItem>
</StatusBar>
</Grid>
</Window>using System.Text;
using System.Windows;
namespace demo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private readonly YieldExamples _examples;
private readonly StringBuilder _logBuilder = new();
public MainWindow()
{
InitializeComponent();
_examples = new YieldExamples();
_examples.OnLog += message => Log(message);
}
#region 日志方法
private void Log(string message)
{
Dispatcher.Invoke(() =>
{
txtOutput.AppendText(message + Environment.NewLine);
txtOutput.ScrollToEnd();
});
}
private void LogTitle(string title)
{
Log($"\n{'='.ToString().PadRight(60, '=')}");
Log($"{title}");
Log($"{'='.ToString().PadRight(60, '=')}\n");
}
private void SetStatus(string status)
{
txtStatus.Text = status;
}
#endregion
#region 获取数量
private int GetCount()
{
if (int.TryParse(txtCount.Text, out int count) && count > 0)
{
return count;
}
return 10;
}
#endregion
/// <summary>
/// 斐波那契数列
/// </summary>
private void Fibonacci_Click(object sender, RoutedEventArgs e)
{
int count = GetCount();
LogTitle($"斐波那契数列 (前 {count} 个)");
Log("代码:foreach (var num in Fibonacci().Take(count))");
Log("");
int index = 0;
foreach (var num in _examples.Fibonacci().Take(count))
{
Log($"F({index}) = {num}");
index++;
}
Log("\n 注意:虽然 Fibonacci() 是无线序列,但只计算需要的数量");
SetStatus($"已生成{count} 个 斐波那契数");
}
/// <summary>
/// 质数生成
/// </summary>
private void Primes_Click(object sender, RoutedEventArgs e)
{
int count = GetCount();
LogTitle($"质数生成器 (前 {count} 个)");
var primes = _examples.Primes().Take(count).ToList();
Log($"质数列表:{string.Join(",", primes)}");
Log($"\n总生成了 {primes.Count} 个质数");
SetStatus($"已生成 {count} 个质数");
}
/// <summary>
/// 延迟执行演示
/// </summary>
private void LazyExecution_Click(object sender, RoutedEventArgs e)
{
LogTitle("延迟执行演示");
Log("步骤1: 调用迭代器方法(不会执行任何代码)");
Log(">>> var iterator = LazyExecutionDemo();");
var iterator = _examples.LazyExecutionDemo();
Log(">>> 迭代器已创建,但方法内部代码尚未执行!\n");
Log("步骤2: 开始遍历(此时才真正执行)");
Log(">>> foreach (var value in iterator)");
Log("");
foreach (var value in iterator)
{
Log($" [调用方] ★ 收到值: {value}");
Log("");
}
Log("\n✅ 关键点:");
Log(" 1. 调用迭代器方法时,方法体不会执行");
Log(" 2. 每次 MoveNext() 时,执行到下一个 yield return 暂停");
Log(" 3. 状态被保存,下次从暂停处继续");
SetStatus("延迟执行演示完成");
}
/// <summary>
/// yield break 演示
/// </summary>
private void YieldBreak_Click(object sender, RoutedEventArgs e)
{
LogTitle("yield break 演示");
var numbers = new[] { 1, 5, 3, 8, -2, 10, 7 };
Log($"原始数组: [{string.Join(", ", numbers)}]");
Log("遇到负数时使用 yield break 终止迭代\n");
var result = _examples.TakeUntilNegative(numbers).ToList();
Log($"\n结果: [{string.Join(", ", result)}]");
Log("\n✅ yield break 可以提前终止迭代,类似于普通方法中的 return");
SetStatus("yield break 演示完成");
}
/// <summary>
/// 自定义LINQ
/// </summary>
private void CustomLinq_Click(object sender, RoutedEventArgs e)
{
LogTitle("自定义 LINQ 操作符(使用 yield 实现)");
var numbers = Enumerable.Range(1, 20);
Log($"原始数据: 1 到 20\n");
// 使用自定义的 Where 和 Select
var result = _examples.MySelect(
_examples.MyWhere(numbers, x => x % 2 == 0), // 筛选偶数
x => x * x // 计算平方
);
Log("操作: 筛选偶数,然后计算平方");
Log("代码: MySelect(MyWhere(numbers, x => x % 2 == 0), x => x * x)");
Log("");
var resultList = result.Take(5).ToList();
Log($"前5个结果: [{string.Join(", ", resultList)}]");
Log("\n✅ LINQ 的 Where、Select 等方法底层就是用 yield 实现的");
SetStatus("自定义LINQ演示完成");
}
/// <summary>
/// 树遍历
/// </summary>
private void TreeTraversal_Click(object sender, RoutedEventArgs e)
{
LogTitle("使用 yield 进行树遍历");
Log("树结构:");
Log(" 根节点");
Log(" ├── 子节点1");
Log(" │ ├── 孙节点1-1");
Log(" │ └── 孙节点1-2");
Log(" ├── 子节点2");
Log(" │ └── 孙节点2-1");
Log(" └── 子节点3");
Log("");
var tree = _examples.CreateSampleTree();
Log("前序遍历结果:");
int index = 1;
foreach (var node in _examples.PreOrderTraversal(tree))
{
Log($" {index}. {node.Name}");
index++;
}
Log("\n✅ yield 使递归遍历代码更简洁、更易读");
SetStatus("树遍历演示完成");
}
/// <summary>
/// 分页数据
/// </summary>
private async void PagedData_Click(object sender, RoutedEventArgs e)
{
LogTitle("模拟分页数据获取");
Log("场景:每页3条数据,共获取2页");
Log("使用 yield 实现按需加载,只有遍历时才请求下一页\n");
SetStatus("正在获取数据...");
await Task.Run(() =>
{
int count = 0;
foreach (var item in _examples.GetPagedData(3, 2))
{
Log($" 收到: {item}");
count++;
// 演示:只取前4条就停止
if (count >= 4)
{
Log("\n (只取了4条就停止,第2页不会完整获取)");
break;
}
}
});
Log("\n✅ yield 适合处理分页/流式数据,按需获取,节省资源");
SetStatus("分页数据演示完成");
}
#region 清空
private void Clear_Click(object sender, RoutedEventArgs e)
{
txtOutput.Clear();
SetStatus("已清空");
}
#endregion
}
}namespace demo
{
public class YieldExamples
{
public event Action<string>? OnLog;
private void Log(string message) => OnLog?.Invoke(message);
#region 斐波那契数列
/// <summary>
/// 生成无限斐波那契数列
/// </summary>
/// <returns></returns>
public IEnumerable<long> Fibonacci()
{
long a = 0, b = 1;
while (true)
{
yield return a;
long temp = a;
a = b;
b = temp + b;
}
}
#endregion
#region 质数生成器
public IEnumerable<int> Primes()
{
// 素数序列的第一个元素是2,立刻返回它,但方法不会结束,而是暂停等待下一次迭代。
yield return 2;
for (int num = 3; ; num += 2)
{
if (IsPrime(num))
{
yield return num; // 每找到一个素数就返回一次。
}
}
}
private bool IsPrime(int number)
{
if (number < 2) return false;
if (number == 2) return true;
if (number % 2 == 0) return false;
int sqrt = (int)Math.Sqrt(number);
for (int i = 3; i <= sqrt; i += 2)
{
if (number % i == 0)
{
return false;
}
}
return true;
}
#endregion
#region 延迟执行演示
/// <summary>
/// 演示延迟执行 - 带日志
/// </summary>
public IEnumerable<int> LazyExecutionDemo()
{
Log(" [迭代器] 方法开始执行");
for (int i = 1; i <= 5; i++)
{
Log($" [迭代器] 准备生成值: {i}");
yield return i;
Log($" [迭代器] 从 yield return {i} 返回后继续");
}
Log(" [迭代器] 方法执行结束");
}
#endregion
#region yield break 演示
/// <summary>
/// 演示 yield break - 遇到负数停止
/// </summary>
public IEnumerable<int> TakeUntilNegative(IEnumerable<int> source)
{
foreach (var item in source)
{
if (item < 0)
{
Log($" 遇到负数 {item},执行 yield break");
yield break;
}
yield return item;
}
}
#endregion
#region 自定义过滤器(模拟LINQ)
/// <summary>
/// 自定义 Where 实现
/// </summary>
public IEnumerable<T> MyWhere<T>(IEnumerable<T> source, Func<T, bool> predicate)
{
foreach (var item in source)
{
if (predicate(item))
yield return item;
}
}
/// <summary>
/// 自定义 Select 实现
/// </summary>
public IEnumerable<TResult> MySelect<T, TResult>(IEnumerable<T> source, Func<T, TResult> selector)
{
foreach (var item in source)
{
yield return selector(item);
}
}
#endregion
#region 树遍历
public class TreeNode
{
public string Name { get; set; } = "";
public List<TreeNode> Children { get; set; } = new();
}
/// <summary>
/// 前序遍历
/// </summary>
public IEnumerable<TreeNode> PreOrderTraversal(TreeNode? node)
{
if (node == null) yield break;
yield return node;
foreach (var child in node.Children)
{
foreach (var descendant in PreOrderTraversal(child))
{
yield return descendant;
}
}
}
/// <summary>
/// 创建示例树
/// </summary>
public TreeNode CreateSampleTree()
{
return new TreeNode
{
Name = "根节点",
Children = new List<TreeNode>
{
new TreeNode
{
Name = "子节点1",
Children = new List<TreeNode>
{
new TreeNode { Name = "孙节点1-1" },
new TreeNode { Name = "孙节点1-2" }
}
},
new TreeNode
{
Name = "子节点2",
Children = new List<TreeNode>
{
new TreeNode { Name = "孙节点2-1" }
}
},
new TreeNode { Name = "子节点3" }
}
};
}
#endregion
#region 分页数据模拟
/// <summary>
/// 模拟分页获取数据
/// </summary>
public IEnumerable<string> GetPagedData(int pageSize, int maxPages)
{
for (int page = 1; page <= maxPages; page++)
{
Log($" 正在获取第 {page} 页数据...");
Thread.Sleep(500); // 模拟网络延迟
for (int i = 1; i <= pageSize; i++)
{
yield return $"Page{page}-Item{i}";
}
}
}
#endregion
}
}