我在yield return x;
C#方法中看到的所有示例都可以通过返回整个列表以相同的方式完成.在这些情况下,使用yield return
语法与返回列表是否有任何好处或优势?
此外,在使用哪种类型的方案时yield return
,您不能只返回完整列表?
但是如果你自己建造一个系列呢?
通常,迭代器可用于延迟生成一系列对象.例如,Enumerable.Range
方法内部没有任何类型的集合.它只是按需生成下一个数字.使用状态机生成这种延迟序列有很多用途.其中大多数都涵盖在函数式编程概念中.
在我看来,如果你把迭代器看作是枚举集合的一种方式(它只是最简单的用例之一),那你就走错了路.正如我所说,迭代器是返回序列的手段.序列甚至可能是无限的.没有办法返回无限长度的列表并使用前100个项目.它有偷懒的时候.返回集合与返回集合生成器(迭代器是什么)有很大不同.它将苹果与橙子进行比较.
假设的例子:
static IEnumerableGetPrimeNumbers() { for (int num = 2; ; ++num) if (IsPrime(num)) yield return num; } static void Main() { foreach (var i in GetPrimeNumbers()) if (i < 10000) Console.WriteLine(i); else break; }
此示例打印小于10000的素数.您可以轻松地将其更改为打印少于一百万的数字,而无需触及素数生成算法.在此示例中,您不能返回所有素数的列表,因为序列是无限的,并且消费者甚至不知道它从一开始就想要多少项.
这里的好答案表明,一个好处yield return
是你不需要创建一个列表 ; 列表可能很昂贵.(此外,过了一会儿,你会发现它们笨重而且不够优雅.)
但是如果你没有List怎么办?
yield return
允许您以多种方式遍历数据结构(不一定是列表).例如,如果您的对象是树,则可以按前或后顺序遍历节点,而无需创建其他列表或更改基础数据结构.
public IEnumerableInOrder() { foreach (T k in kids) foreach (T n in k.InOrder()) yield return n; yield return (T) this; } public IEnumerable PreOrder() { yield return (T) this; foreach (T k in kids) foreach (T n in k.PreOrder()) yield return n; }
在您实际调用该特定结果之前,"yield return"迭代器块不会执行任何代码.这意味着它们也可以有效地链接在一起.Pop测验:假设"ReadLines()"函数从文本文件中读取所有行并使用迭代器块实现,以下代码将在文件上迭代多少次?
var query = File.ReadLines(@"C:\MyFile.txt") .Where(l => l.Contains("search text") ) .Select(l => int.Parse(l.SubString(5,8)) .Where(i => i > 10 ); int sum=0; foreach (int value in query) { sum += value; }
答案恰恰是一个,直到foreach
循环中为止.
再次使用foreach
上面的假设函数,我们现在可以轻松地将读取文件的代码与从实际解析结果的代码中过滤掉不需要的行的代码分开.特别是第一个是非常可重复使用的.
请参阅我对这个问题的回答,以获得一个很好的例子:
C#fibonacci函数返回错误
基本上,我使用迭代器块来实现斐波那契序列,该迭代器块永远不会停止(至少在到达MaxInt之前),然后以安全的方式使用该实现.
这是那些东西,是更难用散文来解释一个比它究竟是谁用一个简单的视觉1:
如果您看不到图像,则会显示相同代码的两个版本,并针对不同的问题提供背景突出显示.linq代码具有很好地分组的所有颜色,而传统的命令式代码具有混合的颜色.作者认为(并且我同意)这个结果是使用linq与使用命令式代码的典型结果...... linq在组织代码方面做得更好,以便在各个部分之间获得更好的流程.
1我相信这是最初的来源:https: //twitter.com/mariofusco/status/571999216039542784.另请注意,此代码是Java,但C#类似.
有时您需要返回的序列太大而无法放入内存中.例如,大约3个月前,我参加了一个MS SLQ数据库之间的数据迁移项目.数据以XML格式导出.对于XmlReader,收益率回报非常有用.它使编程变得更加容易.例如,假设一个文件有1000个Customer元素 - 如果您只是将此文件读入内存,则需要将所有文件同时存储在内存中,即使它们是按顺序处理的.因此,您可以使用迭代器逐个遍历集合.在这种情况下,你必须花费一个元素的内存.
事实证明,对我们的项目使用XmlReader是使应用程序工作的唯一方法 - 它工作了很长时间,但至少它没有挂起整个系统并且没有引发OutOfMemoryException.当然,您可以使用XmlReader而不使用yield迭代器.但是迭代器使我的生活变得更加轻松(我不会那么快地编写导入代码而没有麻烦).观看此页面以了解如何使用yield迭代器来解决实际问题(不仅仅是无限序列的科学).
在玩具/演示场景中,没有太大的区别.但是有些情况下,产生迭代器是有用的 - 有时,整个列表不可用(例如流),或者列表计算成本高,并且不可能完全需要.