使用yield如何节省时间或内存
本文关键字:内存 节省时间 yield 使用 | 更新日期: 2023-09-27 18:28:31
我是C#的新手,在我尝试学习的以前的语言中没有见过yield
的等价物,除了可读性之外,我不相信它有帮助。没有它我活了这么多年,为什么我需要它?
正如我在下面所说,您可以使用yield return
逐个吐出T
类型的值,而不是将这些值收集到IEnumerable<T>
中,然后在最后吐出整个集合。有什么意义?毕竟,我确信在中断函数的执行以复制出单个值时会有一些开销。也许我会运行一些性能测试,看看它在时间方面是否更有效。除此之外,我想知道你是否可以向我展示一个特定的情况,在这种情况下,我需要迭代一个函数收集的一组值,并且只能用yield
来完成,或者最好用yield
来完成。
作为迭代器使用的一个典型例子,考虑一个数字序列迭代器:
IEnumerable<int> fibo() {
int cur = 0, next = 1;
while(true) {
yield return cur;
next += cur;
cur = next - cur;
}
}
现在我们可以选择如何处理该系列,并且只计算所需的元素:
var fibs = fibo();
var sumOfFirst10Fibs = fibs.Take(10).Sum();
另一种有用的模式是使复杂的数据结构变平,如树1:
public class Tree<T> {
public Tree<T> Left, Right;
public T value;
public IEnumerable<T> InOrder() {
if(Left != null) {
foreach(T val in Left.InOrder())
yield return val;
}
yield return value;
if(Right != null) {
foreach(T val in Right.InOrder())
yield return val;
}
}
}
}
1正如Alexey在评论中所指出的,按顺序遍历是低效的(尤其是当遍历高大的树时)
其想法是动态生成值您的值集合可能是无限的,或者生成每个值的成本可能很高当您通过IEnumerable
调用foreach
时,实际上是在调用IEnumerator
上的方法,这些方法可以用您喜欢的任何方式实现。使用yield
的函数会自动重新实现为仅在请求值时生成值的IEnumerator
。当您也想要动态生成值时,您还必须对IEnumerator
的实现进行编码,就像yield
ing函数被替换一样。
在某些特定情况下,使用生成器可能比创建和返回集合更可取:
- 逐行搜索一个很大的文件。你不想把几GB的文本加载到内存中,所以读一行并
yield return
是有意义的。当然,你可以写一个循环,但通过将逻辑提取到生成器中,你可以很容易地用数据库表或不同格式的文件替换文件 - 在树上散步。您可以使用访问者来遍历树,也可以使用生成器以正确的顺序生成节点序列,这两种方法是彼此相反的。注意:递归生成器在C#中是个坏主意
- 为测试目的生成无限数据,其中每个连续的元素都使用以前的元素来生成自己("在圣诞节的1298456天,我的真爱送给我…"是一个微不足道的例子,你不需要存储1298455天的礼物,只需要存储以前的礼物和当天的列表)
基本上,在每种情况下,如果您不必担心将IEnumerable
处理为ICollection
,即您将其视为一个值流,而不是具有Count
的有限值包,则可以通过使用生成器来节省时间或内存。
在您想要返回的Collection尚未准备好的情况下,收益率可能很有用。也就是说,你在迭代的同时构建列表。通过使用yield return,您实际上只需要在返回之前拥有下一个项目。如果IEnumerable表示一个无限集,则收益率回报率更可取。考虑素数列表,或者一个无限的随机数列表。您永远不能一次返回完整的IEnumerable,所以您可以使用yield return递增地返回列表。
MSDN涵盖了很多内容:
当您在语句中使用yield关键字时,您指示它出现的方法、运算符或get访问器是迭代器。使用yield定义迭代器消除了对显式迭代器的需要extra类(保存枚举状态的类,请参阅IEnumerator(OfT) 例如)和自定义集合类型的IEnumerator模式。
技术实施
以下代码返回一个IEnumerable<迭代器中的string>方法,然后遍历其元素。
IEnumerable<string> elements = MyIteratorMethod(); foreach (string element in elements) { … }
对MyIteratorMethod的调用不执行该方法的主体。相反,该调用返回IEnumerable<string>插入元素变量
在foreach循环的迭代中,MoveNext方法是需要元素。此调用执行MyIteratorMethod的主体直到到达下一个yield return语句。表达式yield return语句返回的值不仅决定循环体消耗的元素变量,以及元素的当前属性,它是IEnumerable<string>
在每个foreach循环的后续迭代迭代器主体从它停止的地方继续,当它达到收益回报语句。foreach循环在迭代器方法或yield break语句结束。