【问题标题】:Trouble understanding yield in C# [duplicate]无法理解 C# 中的产量 [重复]
【发布时间】:2017-12-01 14:39:40
【问题描述】:

我希望对我最近在调试器中逐步完成但无法真正理解的 sn-p 得到一些澄清。

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

我有这个过于基本的函数,它返回 VendorsIEnumerable 集合(一个简单的类,包含 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 集合(参见 cmets)。

【问题讨论】:

  • yield 是关于延迟执行(流式)
  • 你错过了Deferred execution
  • 干杯。你有什么好的、易于理解的资料可供我阅读吗?我很想用它来编辑问题,因为我确信其他人可能会遇到和我一样的麻烦
  • Another example 的迭代器被剖析,并引用了标准。
  • 我们称之为延迟和延迟执行,见stackoverflow.com/questions/2515796/…

标签: c# .net yield yield-return


【解决方案1】:

这称为延迟执行,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)) { ... }

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

【讨论】:

  • 啊,有趣的东西。那么上面提到的循环基本上会返回 100 和 1000 个?
  • @geostocker 是的,没错。
  • @geostocker 另外,请意识到所有System.Linq 都是懒惰的。 infiniteOnes.Take(100) 也是惰性的,因此前 100 个枚举的结果只有在 foreach 中枚举后才会真正执行。惰性链可以无限循环(infiniteOnes.Take(100).Skip(10) 等),它只有在迭代时才真正开始工作。
【解决方案2】:

来自 msdn:

延迟执行

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

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

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

【讨论】:

    【解决方案3】:

    当您在需要时循环它们时,它会获取这些项目。这种方式说您只需要前 4 个结果,然后您就中断了,它不会产生更多结果,您只是节省了一些处理能力!

    来自MS Docs

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

    注意 - 如果你 .ToList() 对一个 yield 方法的结果,它会像你返回一个列表一样工作,从而违背了 yield 的目的。

    【讨论】:

      猜你喜欢
      • 2022-01-04
      • 2015-03-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-07
      • 1970-01-01
      • 2019-10-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多