如何处理“无限”IEnumerable?

“无限”IEnumerable的一个简单例子

IEnumerable Numbers() { int i=0; while(true) { yield return unchecked(i++); } } 

我知道

 foreach(int i in Numbers().Take(10)) { Console.WriteLine(i); } 

 var q = Numbers(); foreach(int i in q.Take(10)) { Console.WriteLine(i); } 

两者都工作正常(并打印出数字0-9)。

但是在复制或处理像q这样的表达式时是否有任何陷阱? 我可以依赖这样一个事实,即它们总是被评估为“懒惰”吗? 产生无限循环有危险吗?

是的,您可以保证上面的代码会被懒散地执行。 虽然它看起来(在你的代码中)就像你永远循环,你的代码实际上产生这样的东西:

 IEnumerable Numbers() { return new PrivateNumbersEnumerable(); } private class PrivateNumbersEnumerable : IEnumerable { public IEnumerator GetEnumerator() { return new PrivateNumbersEnumerator(); } } private class PrivateNumbersEnumerator : IEnumerator { private int i; public bool MoveNext() { i++; return true; } public int Current { get { return i; } } } 

(这显然不是将要生成的内容,因为这对您的代码非常具体,但它仍然相似,并且应该向您展示为什么它会被懒惰地评估)。

只要你只调用懒惰的,非缓冲的方法,你应该没问题。 所以SkipTakeSelect等都很好。 然而, MinCountOrderBy等会变得疯狂。

它可以工作,但你需要谨慎。 或者注入一个Take(somethingFinite)作为安全措施(或一些其他自定义扩展方法,在太多数据后抛出exception)。

例如:

 public static IEnumerable SanityCheck(this IEnumerable data, int max) { int i = 0; foreach(T item in data) { if(++i >= max) throw new InvalidOperationException(); yield return item; } } 

你必须避免任何试图读取的贪婪函数。 这将包括Enumerable扩展,如: CountToArray / ToList和聚合Avg / Min / Max等。

无限懒惰列表没有任何问题,但你必须有意识地决定如何处理它们。

使用Take通过设置上限来限制无限循环的影响,即使您不需要它们也是如此。

是的,您的代码将始终无需无限循环即可运行。 有些人可能会在以后出现,并搞砸了。 假设他们想做:

 var q = Numbers().ToList(); 

然后,你被冲了! 许多“聚合”函数会像Max()一样杀死你。

如果它不是懒惰的评估,那么您的第一个示例将首先无法按预期工作。