C#中的Yield return:面向初学者的惰性魔法

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

C#中的Yield return:当代码“懒惰”地工作时,这很酷

\n\n

想象一下,你正在编写一个程序,需要处理来自日志文件的一百万行数据。你诚实地将所有内容加载到一个 List<string> 列表中,将其传递给过滤方法,然后——砰!——程序卡住了,内存泄漏,用户紧张地按下 Ctrl+Alt+Del。

\n\n

熟悉吗?如果你至少遇到过一次这种情况,那么是时候了解 C# 的“惰性魔法”——yield return 运算符了。这不仅仅是语法糖,而是一个强大的工具,可以彻底改变你处理集合的方式。

\n\n

在本文中,我们将通过实际示例解析 yield return 的正确使用,深入探讨其内部机制,并弄清楚何时使用它,何时最好远离它。开始吧!

\n\n

什么是 yield return,它是如何工作的?

\n\n

简单来说,yield return 是一种告诉编译器的方式:“嘿,我不想立即返回整个集合。让我根据需要逐个返回元素。”这种方法被称为 惰性计算(lazy evaluation)。

\n\n

下面是一个经典示例:

\n\n
public IEnumerable<int> GetNumbers()\n{\n    yield return 1;\n    yield return 2;\n    yield return 3;\n}
\n\n

调用此方法时,你不会得到一个数组 [1, 2, 3]。相反,你会得到一个迭代器,它会在你请求时逐个返回数字。每次调用 MoveNext() 都会将方法的执行推进到下一个 yield return。

\n\n

这就像一条流水线:元素仅在消费者需要时才被生成。没有多余的复制,没有内存中的巨大列表——只有纯粹、高效的工作。

\n\n

为什么 yield return 不仅仅是“返回列表”?

\n\n

许多初学者会想:“为什么我需要 yield return,如果我可以直接收集一个 List 并返回它?”让我们通过一个具体示例来理解。

\n\n

想象一下,你需要从一个无限序列中获取前10个偶数。使用 yield return 可以优雅地实现:

\n\n
public IEnumerable<int> GetEvenNumbers()\n{\n    int i = 0;\n    while (true)\n    {\n        yield return i;\n        i += 2;\n    }\n}\n\n// 使用\nvar firstTenEven = GetEvenNumbers().Take(10).ToList();
\n\n

尝试用 List<int> 做同样的事情——要么你会陷入无限循环,要么你会将所有生成的数字存储在内存中。Yield return 让我们能够处理潜在的无限序列,而不会占用内存。

\n\n

实际示例:读取大文件

\n\n

回到我们那个百万行的日志文件。以下是初学者通常的写法:

\n\n
public List<string> ReadLogLines(string path)\n{\n    var lines = new List<string>();\n    using (var reader = new StreamReader(path))\n    {\n        string line;\n        while ((line = reader.ReadLine()) != null)\n        {\n            lines.Add(line);\n        }\n    }\n    return lines;\n}
\n\n

问题:整个文件被加载到内存中。如果文件大小为500 MB,那么列表也会占用大约相同的大小。而如果你只需要过滤出错误行呢?你仍然会加载所有内容。

\n\n

下面是 yield return 的正确使用方式:

\n\n
public IEnumerable<string> ReadLogLines(string path)\n{\n    using (var reader = new StreamReader(path))\n    {\n        string line;\n        while ((line = reader.ReadLine()) != null)\n        {\n            yield return line;\n        }\n    }\n}\n\n// 无需加载整个文件即可进行过滤\nvar errorLines = ReadLogLines("log.txt")\n    .Where(line => line.Contains("ERROR"))\n    .Take(100);<                

博客

书籍推荐