无法理解C#中的产量

时间:2017-06-28 09:42:13

标签: c# .net yield yield-return

我希望能够对我最近在调试器中完成的一个片段做一些澄清,但是根本无法理解。

我正在 PluralSight 上参加 C#课程,当前主题位于yield,并返回带有关键字的IEnumerable<T>

我有这个过于基本的函数,它会返回IEnumerable Vendors的集合(一个包含IdCompanyNameEmail的简单类:

public IEnumerable<Vendor> RetrieveWithIterator()
{
    this.Retrieve(); // <-- I've got a breakpoint here
    foreach(var vendor in _vendors)
    {
        Debug.WriteLine($"Vendor Id: {vendor.VendorId}");
        yield return vendor;
    }
}

我在单元测试中得到了这个代码,我用它来测试函数:

var vendorIterator = repository.RetrieveWithIterator(); // <-- Why don't it enter function?
foreach (var item in vendorIterator) // <-- But starts here?
{
    Debug.WriteLine(item);
}
var actual = vendorIterator.ToList();

我真的无法理解,而且我确信很多初学者都遇到同样的问题,这就是为什么对RetrieveWithIterator的初始调用没有启动该功能,而是开始当我们开始迭代它返回的IEnumerable集合时(参见注释)。

3 个答案:

答案 0 :(得分:12)

这称为延迟执行,yield是懒惰的,只能按需要运行。

这有很多优点,其中之一就是你可以创建看似无限的枚举:

public IEnumerable<int> InfiniteOnes()
{
     while (true)
         yield 1;
}

现在想象如下:

var infiniteOnes = InfiniteOnes();

急切地执行,你会非常高兴地发现StackOverflow例外。

另一方面,因为它很懒,你可以做到以下几点:

var infiniteOnes = InfiniteOnes();
//.... some code
foreach (var one in infiniteOnes.Take(100)) { ... }

后来,

foreach (var one in infiniteOnes.Take(10000)) { ... }

迭代器块只在需要时运行;当枚举被迭代时,而不是之前,而不是之后。

答案 1 :(得分:2)

来自msdn:

延期执行

延迟执行意味着表达式的评估被延迟,直到实际需要它的实现值。当您必须操作大型数据集合时,延迟执行可以极大地提高性能,尤其是在包含一系列链式查询或操作的程序中。在最好的情况下,延迟执行只能通过源集合进行单次迭代。

在迭代器块中使用时,yield关键字(以yield-return语句的形式)直接在C#语言中支持延迟执行。这样的迭代器必须返回类型IEnumeratorIEnumerator<T>(或派生类型)的集合。

var vendorIterator = repository.RetrieveWithIterator(); // <-- Lets deferred the execution
foreach (var item in vendorIterator) // <-- execute it because we need it
{
    Debug.WriteLine(item);
}
var actual = vendorIterator.ToList();

渴望与懒惰评估

当你编写一个实现延迟执行的方法时,你还必须决定是否使用延迟评估或急切评估来实现该方法。

  • 在延迟评估中,在每次调用迭代器期间处理源集合的单个元素。这是实现迭代器的典型方法。
  • 在急切的评估中,对迭代器的第一次调用将导致整个集合被处理。也可能需要源集合的临时副本。

延迟评估通常会产生更好的性能,因为它在整个集合评估过程中均匀分配开销处理,并最大限度地减少临时数据的使用。当然,对于某些操作,除了实现中间结果之外别无选择。

source

答案 2 :(得分:1)

当你在需要的时候循环它们时它会得到物品。这种方式说你只需要前4个结果,然后你就打破了,它不会产生更多的东西,你只是节省了一些处理能力!

来自MS Docs

  

使用yield return语句一次返回一个元素。   您可以使用foreach语句或LINQ查询来使用迭代器方法。 foreach循环的每次迭代都会调用迭代器方法。在迭代器方法中达到yield return语句时,将返回表达式,并保留代码中的当前位置。下次调用迭代器函数时,将从该位置重新开始执行。   您可以使用yield break语句来结束迭代。

注意 - 如果你对产生的方法的结果.ToList(),它将像返回单个列表一样工作,从而破坏了yield的目的。