导读:本期聚焦于小伙伴创作的《C#中Yield关键字怎么用?协程与迭代器背后的原理与实战》,敬请观看详情。不少人写C#时只把yield return当成返回集合的语法糖,却不清楚它如何延迟执行并支撑迭代器状态机。实际上编译器会把含yield的方法转为实现了IEnumerable和IEnumerator的类,每次调用MoveNext才执行到下个断点。这种机制不仅能大幅降低大批量数据的内存占用,还能模拟类似Unity协程的暂停恢复逻辑。本文从IL层面讲清状态机流转,再用文件读取与异步任务调度两个例子展示yield在真实项目里的用法,并点出常见误用导致的一次性遍历陷阱。

在C#语言里,yield关键字常常被初学者误认为只是简化返回列表的写法,其实它背后是一整套迭代器状态机机制。理解yield的工作方式,不仅能帮你写出更省内存的数据处理逻辑,还能用纯C#模拟出类似游戏开发里协程的暂停与恢复能力。

yield到底是什么

当在一个方法中使用yield returnyield break时,编译器并不会让这个方法像普通函数那样顺序执行完再返回数组,而是自动生成一个隐藏的状态机类。这个类实现了IEnumerableIEnumerator接口,方法的每一段代码被切分成不同的状态编号。

调用包含yield的方法本身不会立刻执行方法体,它只是返回一个迭代器对象。只有当你用foreach循环或者手动调用MoveNext时,代码才会跑到下一个yield return处暂停,并把当前值交出来。下次再调用MoveNext,就从暂停点继续执行。这种惰性求值特性,是yield最核心的价值。

using System;
using System.Collections.Generic;

class Demo
{
    static IEnumerable<int> CountToThree()
    {
        Console.WriteLine("开始");
        yield return 1;
        Console.WriteLine("到1");
        yield return 2;
        Console.WriteLine("到2");
        yield return 3;
        Console.WriteLine("结束");
    }

    static void Main()
    {
        var it = CountToThree();
        Console.WriteLine("还没进方法体");
        foreach (var n in it)
        {
            Console.WriteLine(n);
        }
    }
}

上面这段代码运行后会先打印“还没进方法体”,然后才逐步进入CountToThree。这说明方法体是按需执行的,而不是一次性算完。

用yield实现自定义迭代器

在没有yield之前,想写一个自定义迭代器需要实现完整的IEnumerator,要写Current、MoveNext、Reset等成员,非常繁琐。yield让编译器代劳了这些样板代码,你只需关心每次产出什么数据。

比如我们要从一个大文件里逐行读取并处理,如果一次性读入内存可能造成溢出。用yield可以按行产出,调用方一边遍历一边处理,内存占用始终很小。

using System;
using System.Collections.Generic;
using System.IO;

class FileReader
{
    static IEnumerable<string> ReadLinesLazy(string path)
    {
        using (var sr = new StreamReader(path))
        {
            string line;
            while ((line = sr.ReadLine()) != null)
            {
                yield return line;
            }
        }
    }

    static void Main()
    {
        foreach (var line in ReadLinesLazy("test.txt"))
        {
            if (line.Contains("error"))
            {
                Console.WriteLine(line);
            }
        }
    }
}

这个例子里,即使文件有几十GB,只要每行不算太长,程序也能平稳运行。因为任意时刻内存里只有当前这一行。这就是迭代器最实用的场景之一。

yield与协程的关系

严格来说C#语言层面并没有像Unity那样名为协程的关键字,但借助yield的状态暂停能力,可以模拟出协程行为。思路是把一个长时间任务拆成多步,每步用yield return让出执行权,外层调度器在合适时机继续推进。

下面写一个简单的任务调度器,它顺序执行多个返回迭代器的“协程”方法,每次只推进一格,从而表现出协作式多任务的效果。

using System;
using System.Collections.Generic;

class CoroutineDemo
{
    static IEnumerable<int> TaskA()
    {
        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine("A步" + i);
            yield return i;
        }
    }

    static IEnumerable<int> TaskB()
    {
        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine("B步" + i);
            yield return i;
        }
    }

    static void Run(params IEnumerable<int>[] tasks)
    {
        var enumerators = new List<IEnumerator<int>>();
        foreach (var t in tasks)
        {
            enumerators.Add(t.GetEnumerator());
        }
        bool any;
        do
        {
            any = false;
            foreach (var e in enumerators)
            {
                if (e.MoveNext())
                {
                    any = true;
                }
            }
        } while (any);
    }

    static void Main()
    {
        Run(TaskA(), TaskB());
    }
}

运行后你会看到A和B的步骤交替出现,这便是用yield实现的极简协程调度。在真实项目里,可以把yield return后面换成等待条件、时间段或子协程,做出更复杂的流程控制。

常见误区与注意点

第一个误区是以为yield方法返回的集合可以反复遍历。实际上迭代器是一次性消费,第二次foreach会重新走一遍方法体,如果方法里有读文件或取随机数,结果可能不同甚至报错。需要多次使用时,用ToList缓存起来。

第二个误区是在yield方法里写return语句。yield方法里只能用yield returnyield break,普通return会导致编译错误。另外,yield不能出现在匿名方法或unsafe块中,这些限制都是状态机生成规则决定的。

写法是否合法说明
yield return 1;合法产出单个元素并暂停
yield break;合法提前结束迭代
return list;不合法yield方法中禁止普通return
在lambda里写yield不合法状态机无法嵌入匿名函数

掌握这些边界,你才能在团队代码里放心使用yield,既写出简洁的迭代器,又不会埋下隐蔽的逻辑坑。

总结

yield关键字远不止是少写几行代码的语法糖。它基于编译器生成的状态机,把方法切成可暂停的片段,从而支撑惰性迭代与协程式调度。在数据处理、流式读取、任务编排中都有极高实用价值。只要避开一次性遍历和错误返回写法,就能把这块特性用得很稳。

C#_Yield迭代器协程修改时间:2026-08-05 15:42:39

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。