目录

迭代器 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
    }
}