【问题标题】:yield return method optimized away when it shouldn't beyield return 方法在不应该被优化的时候被优化掉了
【发布时间】:2019-05-12 09:35:02
【问题描述】:

我偶然发现了我的库的一个测试问题,其中 yield return 因我不理解的原因而被优化。

概念证明代码:

static IEnumerable<int> Sample(int count)
{
    for (int i = 0; i < count; i++) yield return i;
}
static IEnumerable<int> ForEach(IEnumerable<int> items, Action<int> action)
{
    foreach (int item in items) { action(item); yield return item; }
}
static void After(IEnumerable<int> items, Action action)
{
    action();
}

static void Main(string[] args)
{
    int item = -1;
    After(ForEach(Sample(10), v => item = v), () => Console.WriteLine(item));
    Console.WriteLine(item);
    Console.ReadKey();
}

我希望输出是9,然后是9

实际输出是-1,然后是-1

IEnumerables 在SampleForEach 的初始化被优化掉了,作为副作用item 内部Main 永远不会改变。

为什么Sample 没有被迭代到ForEach

真的需要在After 迭代items 吗?

如果是,为什么优化如此之深,以至于即使在ForEach 的迭代也被暂停直到被激发,从而使输出-1 然后9 也成为可能?

【问题讨论】:

  • 如果你不迭代项目,ForEach 方法将永远不会被调用。例如,您可以使用ForEach(...).ToList()
  • 您的 after 方法永远不会调用 items.GetEnumerator。请记住,yield return惰性评估
  • 不相关,但有一个内置的Enumerable.Range 方法用于生成简单序列。
  • Dai,阅读第一个问题 - 为什么Sample 没有被迭代到ForEach
  • @Luka967 因为ForEach 方法也是一个惰性求值状态机函数。只需使用yield return,编译器就会重写您的方法以进行延迟评估,其中包括after 和您的ForEach

标签: c# ienumerable compiler-optimization yield-return


【解决方案1】:

IEnumerable&lt;T&gt; 对象具有延迟执行,这意味着它们在被访问以进行迭代时被执行。当我们开始迭代或将其对象具体化为 List&lt;T&gt;Array 时,它实际上会被调用。

如果您将ToList() 调用添加到您的ForEach 方法,您将看到预期的输出:

After(ForEach(Sample(10), v => item = v).ToList(), () => Console.WriteLine(item));

如您所见,您实现了ForEach() 方法的结果IEnumerable,它也会调用Sample() 并执行。

您可以修改您的方法,如下所示,以查看实际调用该方法的时间:

static IEnumerable<int> Sample(int count)
{
    Console.WriteLine("Sample Invoked");
    for (int i = 0; i < count; i++)
        yield return i;
}

static IEnumerable<int> ForEach(IEnumerable<int> items, Action<int> action)
{
    Console.WriteLine("ForEach Invoked:");
    foreach (int item in items)
    {
        action(item);
        yield return item;
    }
}

然后这样调用:

int item = -1;
After(ForEach(Sample(10), v => item = v), () => Console.WriteLine(item));
Console.WriteLine(item);
    
After(ForEach(Sample(10), v => item = v).ToList(), () => 
Console.WriteLine(item));
Console.WriteLine(item);

输出是:

-1

-1

ForEach 调用:

调用示例

9

9

查看Fiddle DEMO 以观察它的运行情况。

【讨论】:

  • 我知道IEnumerables 被延迟评估,但我从未听说过 C# 的延迟执行。谢谢你的解释!
猜你喜欢
  • 1970-01-01
  • 2015-03-22
  • 1970-01-01
  • 1970-01-01
  • 2012-09-20
  • 1970-01-01
  • 1970-01-01
  • 2023-03-15
  • 2014-12-09
相关资源
最近更新 更多