27

“无限” IEnumerable 的一个简单示例是

IEnumerable<int> 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?我可以相信他们总是被评价为“懒惰”的事实吗?产生无限循环有什么危险吗?

4

5 回答 5

21

只要你只调用惰性的、无缓冲的方法,你应该没问题。所以Skip, Take,Select等都很好。但是,,Min等会发疯。CountOrderBy

它可以工作,但你需要小心。或注入 aTake(somethingFinite)作为安全措施(或其他一些在数据过多后引发异常的自定义扩展方法)。

例如:

public static IEnumerable<T> SanityCheck<T>(this IEnumerable<T> data, int max) {
    int i = 0;
    foreach(T item in data) {
        if(++i >= max) throw new InvalidOperationException();
        yield return item;
    }
}
于 2010-04-29T19:03:21.510 回答
8

是的,您可以保证上面的代码将被延迟执行。虽然它看起来(在您的代码中)就像您会永远循环,但您的代码实际上会产生如下内容:

IEnumerable<int> Numbers()
{
    return new PrivateNumbersEnumerable();
}

private class PrivateNumbersEnumerable : IEnumerable<int>
{
    public IEnumerator<int> GetEnumerator() 
    { 
        return new PrivateNumbersEnumerator(); 
    }
}

private class PrivateNumbersEnumerator : IEnumerator<int>
{
    private int i;

    public bool MoveNext() { i++; return true; }   

    public int Current
    {
        get { return i; }
    }
}

(这显然不是将生成的确切内容,因为这非常特定于您的代码,但它仍然相似并且应该向您展示为什么它会被延迟评估)。

于 2010-04-29T19:02:07.437 回答
5

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

无限惰性列表没有错,但您必须有意识地决定如何处理它们。

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

于 2010-04-29T19:03:22.507 回答
2

是的,您的代码将始终在没有无限循环的情况下工作。稍后有人可能会出现并把事情搞砸。假设他们想做:

var q = Numbers().ToList();

那么,你就被喷了!许多“聚合”函数会杀死你,比如Max().

于 2010-04-29T19:04:44.060 回答
0

如果不是惰性评估,那么您的第一个示例一开始就不会按预期工作。

于 2010-04-29T19:04:47.890 回答