如何处理“无限”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; } } }
(这显然不是将要生成的内容,因为这对您的代码非常具体,但它仍然相似,并且应该向您展示为什么它会被懒惰地评估)。
只要你只调用懒惰的,非缓冲的方法,你应该没问题。 所以Skip
, Take
, Select
等都很好。 然而, Min
, Count
, OrderBy
等会变得疯狂。
它可以工作,但你需要谨慎。 或者注入一个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
扩展,如: Count
, ToArray
/ ToList
和聚合Avg
/ Min
/ Max
等。
无限懒惰列表没有任何问题,但你必须有意识地决定如何处理它们。
使用Take
通过设置上限来限制无限循环的影响,即使您不需要它们也是如此。
是的,您的代码将始终无需无限循环即可运行。 有些人可能会在以后出现,并搞砸了。 假设他们想做:
var q = Numbers().ToList();
然后,你被冲了! 许多“聚合”函数会像Max()
一样杀死你。
如果它不是懒惰的评估,那么您的第一个示例将首先无法按预期工作。