如何使用yield节省时间或内存?

时间:2015-12-30 06:48:02

标签: c# .net algorithm optimization

我是C#的新手,在我以前尝试学习的语言中没有看到相当于yield的内容,并且除了可读性之外我不相信它是有用的。这些年我没有它就活了下来,为什么我需要它呢?

正如我所说的那样,您可以使用yield return逐个吐出T类型的值,而不是将这些值收集到IEnumerable<T>中,然后将整个集合吐出来结束。重点是什么?毕竟,我确定在中断函数执行以复制单个值时会涉及一些开销。也许我会进行一些性能测试,看看它在时间上是否更有效。更重要的是,我想知道你是否可以告诉我一个特定的情况,我需要迭代一个函数收集的一组值,并且只能用yield来做,或者更好的做法它与yield

4 个答案:

答案 0 :(得分:4)

我们的想法是即时生成值。 您的值集合可能是无限的,或者生成每个值的成本可能很高。当您foreach通过IEnumerable时,您实际上是在{{1}上调用方法},可以用你喜欢的任何方式实现。使用IEnumerator的函数会自动重新实现为yield,仅在请求时生成值。当您想要动态生成值时,您还必须编写IEnumerator的实现,就像替换IEnumerator函数的实现一样。

使用生成器的某些特定情况可能比创建和返回集合更可取:

  • 逐行搜索一个非常大的文件。您不希望将几千兆字节的文本加载到内存中,因此读取一行并yield它是有意义的。当然,您可以编写循环,但通过将逻辑提取到生成器中,您可以轻松地将文件替换为数据库表或不同格式的文件,例如
  • 走树。您可以使用访问者走树,或者您可以使用生成器以正确的顺序生成一系列节点,两种方法是相互反转。注意:递归生成器在C#中是一个坏主意!
  • 为测试目的生成无限数据,其中每个连续元素使用以前的元素来生成自己(“在圣诞节的1298456天,我的真爱给我发送......”是一个简单的例子,你不需要存储1298455天值得礼物,只是以前的礼物和当天的名单)

基本上,在每种情况下,您都不必担心将yield return视为IEnumerable,即您将其视为价值流,而不是{{1}的有限值。 1}},您可以使用生成器来节省时间或内存。

答案 1 :(得分:4)

作为迭代器用法的一个例子,请考虑一个数字系列迭代器:

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在评论中指出的那样,有序遍历效率低下(特别是在遍历高大的树木时)。

答案 2 :(得分:0)

MSDN涵盖了很多内容:

  

在语句中使用yield关键字时,表示该   方法,运算符或获取它的访问器是迭代器。   使用yield来定义迭代器不需要显式   额外的类(保存枚举状态的类,请参阅   IEnumerator(Of T)的例子)当你实现IEnumerable时   和自定义集合类型的IEnumerator模式。

     

技术实施

     

以下代码返回IEnumerable&lt; string&gt;来自迭代器   方法,然后遍历其元素。

IEnumerable<string> elements = MyIteratorMethod();
foreach (string element in elements)
{
   …
}
     

对MyIteratorMethod的调用不会执行方法的主体。   相反,该调用返回IEnumerable&lt; string&gt;进入元素   变量。
  在foreach循环的迭代中,MoveNext方法是   要求元素。此调用执行MyIteratorMethod的主体   直到达到下一个yield return语句。表达方式   yield return语句返回的不仅仅是确定值   由循环体消耗的元素变量,以及   元素的当前属性,它是IEnumerable&lt; string&gt;。
  在每一个上   foreach循环的后续迭代,执行   迭代器体从它停止的地方继续,再次停止它   达到收益率报表。当foreach循环完成时   到达iterator方法的结尾或yield break语句。

答案 3 :(得分:0)

yield在您想要返回的Collection尚未就绪的场景中非常有用。即你在迭代时建立列表。通过使用yield-return,您实际上只需要在返回之前拥有下一个项目。 另一种情况,其中yield-return是优选的,如果IEnumerable表示无限集。考虑素数列表,或无限的随机数列表。你永远不能一次返回完整的IEnumerable,所以你使用yield-return来逐步返回列表。