【问题标题】:How to handle an "infinite" IEnumerable?如何处理“无限”的 IEnumerable?
【发布时间】:2011-02-13 23:06:53
【问题描述】:

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

【问题讨论】:

  • 只是为了挑剔,当 i = Int32.MaxValue 并且您执行 i++ 时,您的“无限”示例不会抛出异常吗?还是循环到 Int32.MinValue?嗯.....
  • 你是对的。它可能会引发溢出异常......我会编辑它。
  • 只是在这里挑剔,您的观点仍然存在。 :) 另外,我试过了,它确实循环到 Int32.MinValue。没有OverflowException,所以它实际上是一个无限循环。
  • C# 默认编译时不进行溢出检查。安全的方法总是写 unchecked(i++) 因为你可以用 /checked 编译,然后它会吐。使用 unchecked() 告诉编译器即使使用 /checked 编译也会发生溢出。
  • 比回到原版更漂亮:D

标签: c# enumeration infinite-loop yield-return


【解决方案1】:

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

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; }
    }
}

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      var q = Numbers().ToList();
      

      然后,你被水洗了!许多“聚合”函数会杀死你,比如Max()

      【讨论】:

        【解决方案4】:

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

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

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

        【讨论】:

          【解决方案5】:

          只要你只调用惰性的、无缓冲的方法就可以了。所以SkipTakeSelect 等都可以。但是,MinCountOrderBy 等会发疯。

          它可以工作,但你需要小心。或注入Take(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;
              }
          }
          

          【讨论】:

          • +1 我有一个相互竞争的答案,但我只需要为 IEnumerable 的扩展方法 SanityCheck 投赞成票。有史以来最好的函数名称。我要去执行那个。 . .
          • +1 指出这一点。此外,如果 OP 实际调用 foreach(int i in Numbers()) 也会导致问题。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-09-13
          • 2011-02-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多