为什么linq没有实现完全懒惰?

时间:2017-01-13 14:26:30

标签: c# linq

我有以下代码,我认为它会起作用:

static int ComputerFailsOnTrue(bool flag)
{
    if (flag)
        throw new Exception();  
    return 10; // not relevant
}

static IEnumerable<double> StartComputer()
{
    yield return ComputerFailsOnTrue(true);
    yield return ComputerFailsOnTrue(false);
    yield return ComputerFailsOnTrue(false);
}

static public void Main()
{
    foreach (var item in StartComputer().Skip(1))
    {
        Console.WriteLine($"Hey {item}");
    }
    Console.ReadLine();
}

但它失败了(我将得到异常),因为将计算收集的第一个元素。为什么来自给定集合的moveNext枚举器方法总是计算当前元素? 是假设,电流的计算可以依赖于先前的状态吗?

3 个答案:

答案 0 :(得分:4)

这本身并不是LINQ的失败。这是一个与两件事有关的问题 - IEnumerator<T> interface和C#的iterator methods

IEnumerator<T>只有两个有趣的成员 - MoveNextCurrent。在这样的接口 1 上实现Skip的唯一方法是将MoveNext调用为您希望跳过的项目数量的次数,当然,任何实现MoveNext可以在每个方法调用上自由运行任意代码。它可以做的是避免访问Current

在C#的迭代器实现中,“自动”生成IEnumerable<T>IEnumerator<T>的实现,MoveNextCurrent密切相关 - 你只写一个方法,并且每次该方法获得控制权(对于MoveNext),它还必须计算下一个Current值。

如果您手动实施IEnumerator<T>,则可以在MoveNext方法和某些逻辑中自由放置部分逻辑Current属性,包括评估Current懒惰。在这样的实现中,如果对compute(true)的调用是Current实现的一部分,那么您的代码将按预期工作。

1 在LINQ中可能存在一些使用内置集合类的枚举器绕过的特殊化,但一般来说,这是使用的接口。

答案 1 :(得分:3)

它仍然是懒惰的,只是当你枚举它时,你将枚举跳过Skip(1)的结果需要调用MoveNext两次,并运行编译器生成的状态机{ {1}}方法生成,并抛出异常。

异常不是评估当前项的结果,方法中的所有代码都会在枚举时逐步执行。

如果您致电foo,则不会抛出任何异常。

答案 2 :(得分:2)

在我看来,你误读了延迟和懒惰的概念。这是两个不同的概念,但如果需要它们可以混合使用。

{{1}}