我有以下代码,我认为它会起作用:
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
枚举器方法总是计算当前元素?
是假设,电流的计算可以依赖于先前的状态吗?
答案 0 :(得分:4)
这本身并不是LINQ的失败。这是一个与两件事有关的问题 - IEnumerator<T>
interface和C#的iterator methods。
IEnumerator<T>
只有两个有趣的成员 - MoveNext
和Current
。在这样的接口 1 上实现Skip
的唯一方法是将MoveNext
调用为您希望跳过的项目数量的次数,当然,任何实现MoveNext
可以在每个方法调用上自由运行任意代码。它可以做的是避免访问Current
。
在C#的迭代器实现中,“自动”生成IEnumerable<T>
和IEnumerator<T>
的实现,MoveNext
和Current
密切相关 - 你只写一个方法,并且每次该方法获得控制权(对于MoveNext
),它还必须计算下一个Current
值。
如果您手动实施IEnumerator<T>
,则可以在MoveNext
方法和某些逻辑中自由放置部分逻辑Current
属性,包括评估Current
懒惰。在这样的实现中,如果对compute(true)
的调用是Current
实现的一部分,那么您的代码将按预期工作。
1 在LINQ中可能存在一些使用内置集合类的枚举器绕过的特殊化,但一般来说,这是使用的接口。
答案 1 :(得分:3)
它仍然是懒惰的,只是当你枚举它时,你将枚举跳过Skip(1)
的结果需要调用MoveNext
两次,并运行编译器生成的状态机{ {1}}方法生成,并抛出异常。
异常不是评估当前项的结果,方法中的所有代码都会在枚举时逐步执行。
如果您致电foo
,则不会抛出任何异常。
答案 2 :(得分:2)
在我看来,你误读了延迟和懒惰的概念。这是两个不同的概念,但如果需要它们可以混合使用。
{{1}}