什么时候stackoverflow公平合理?

时间:2013-05-23 11:25:11

标签: c# ienumerable infinite

代码已更新

要修复已过滤的Interminable的错误,请更新以下代码并合并为原始代码:

public static bool IsInfinity(this IEnumerable x) {
    var it=
        x as Infinity??((Func<object>)(() => {
            var info=x.GetType().GetField("source", bindingAttr);
            return null!=info?info.GetValue(x):x;
        }))();

    return it is Infinity;
}

bindingAttr被声明为常量。


  • 摘要

    我正在尝试实现一个无限可枚举的,但遇到一些似乎不合逻辑的东西,并且暂时没有想法。我需要一些方向来完成代码,成为一个语义,逻辑和合理的设计。

  • 整个故事

    几个小时前我问过这个问题:

    Is an infinite enumerable still "enumerable"?

    这可能不是一个好的实施模式。我正在尝试做的是,以逻辑和语义的方式实现一个可枚举的无穷大(我想......)。我会把代码放在这篇文章的最后。

    大问题是,它只是用于呈现无限可枚举,但实际上它的枚举没有任何意义,因为它没有真正的元素。

    因此,除了为枚举提供虚拟元素外,还有四个我能想象的选项,三个导致StackOverflowException

    1. 一旦枚举,就抛出InvalidOperationException

      public IEnumerator<T> GetEnumerator() {
          for(var message="Attempted to enumerate an infinite enumerable"; ; )
              throw new InvalidOperationException(message);
      }
      
    2. 和3.在技术上是等效的,当真的溢出时,让堆栈溢出。

      public IEnumerator<T> GetEnumerator() {
          foreach(var x in this)
              yield return x;
      }
      
      public IEnumerator<T> GetEnumerator() {
          return this.GetEnumerator();
      }
      
    3. (描述于2)

    4. 不要等到它发生,直接抛出StackOverflowException

      public IEnumerator<T> GetEnumerator() {
          throw new StackOverflowException("... ");
      }
      

棘手的事情是:

如果应用option 1,即枚举此枚举,则变为无效操作。说这个灯不是用来照亮(虽然在我的情况下这是真的)并不奇怪。

如果应用option 2option 3,即我们计划堆栈溢出。是否真的像标题一样,只是当stackoverflow是公平合理的时?完全合乎逻辑且合理吗?

最后一个选择是option 4。然而,堆栈实际上并没有真正溢出,因为我们通过抛出 StackOverflowException来阻止它。这让我想起当汤姆克鲁斯扮演的约翰安德顿说:“但它并没有下降。你抓住了它。你阻止它发生的事实并没有改变它将要发生的事实。

避免不合逻辑问题的一些好方法?


代码是可编译且可测试的,请注意在编译之前定义OPTION_1OPTION_4之一。

  • 简单测试

    var objects=new object[] { };
    Debug.Print("{0}", objects.IsInfinity());
    var infObjects=objects.AsInterminable();
    Debug.Print("{0}", infObjects.IsInfinity());
    
  • using System.Collections.Generic;
    using System.Collections;
    using System;
    
    public static partial class Interminable /* extensions */ {
        public static Interminable<T> AsInterminable<T>(this IEnumerable<T> x) {
            return Infinity.OfType<T>();
        }
    
        public static Infinity AsInterminable(this IEnumerable x) {
            return Infinity.OfType<object>();
        }
    
        public static bool IsInfinity(this IEnumerable x) {
            var it=
                x as Infinity??((Func<object>)(() => {
                    var info=x.GetType().GetField("source", bindingAttr);
                    return null!=info?info.GetValue(x):x;
                }))();
    
            return it is Infinity;
        }
    
        const BindingFlags bindingAttr=
            BindingFlags.Instance|BindingFlags.NonPublic;
    }
    
    public abstract partial class Interminable<T>: Infinity, IEnumerable<T> {
        IEnumerator IEnumerable.GetEnumerator() {
            return this.GetEnumerator();
        }
    
    #if OPTION_1
        public IEnumerator<T> GetEnumerator() {
            for(var message="Attempted to enumerate an infinite enumerable"; ; )
                throw new InvalidOperationException(message);
        }
    #endif
    
    #if OPTION_2
        public IEnumerator<T> GetEnumerator() {
            foreach(var x in this)
                yield return x;
        }
    #endif
    
    #if OPTION_3
        public IEnumerator<T> GetEnumerator() {
            return this.GetEnumerator();
        }
    #endif
    
    #if OPTION_4
        public IEnumerator<T> GetEnumerator() {
            throw new StackOverflowException("... ");
        }
    #endif
    
        public Infinity LongCount<U>(
            Func<U, bool> predicate=default(Func<U, bool>)) {
            return this;
        }
    
        public Infinity Count<U>(
            Func<U, bool> predicate=default(Func<U, bool>)) {
            return this;
        }
    
        public Infinity LongCount(
            Func<T, bool> predicate=default(Func<T, bool>)) {
            return this;
        }
    
        public Infinity Count(
            Func<T, bool> predicate=default(Func<T, bool>)) {
            return this;
        }
    }
    
    public abstract partial class Infinity: IFormatProvider, ICustomFormatter {
        partial class Instance<T>: Interminable<T> {
            public static readonly Interminable<T> instance=new Instance<T>();
        }
    
        object IFormatProvider.GetFormat(Type formatType) {
            return typeof(ICustomFormatter)!=formatType?null:this;
        }
    
        String ICustomFormatter.Format(
            String format, object arg, IFormatProvider formatProvider) {
            return "Infinity";
        }
    
        public override String ToString() {
            return String.Format(this, "{0}", this);
        }
    
        public static Interminable<T> OfType<T>() {
            return Instance<T>.instance;
        }
    }
    

4 个答案:

答案 0 :(得分:8)

public IEnumerator<T> GetEnumerator()
{
    while (true)
        yield return default(T);
}

这会创建一个无限的枚举器 - 它上面的foreach永远不会结束,只会继续给出默认值。

请注意,您将无法确定IsInfinity()在代码中编写的方式。这是因为new Infinity().Where(o => o == /*do any kind of comparison*/)仍然是无限的,但会有不同的类型。

答案 1 :(得分:4)

正如您链接的其他帖子中所提到的,无限枚举对于C#进行枚举非常有意义,并且有大量真实世界的例子,人们编写的枚举器永远不会结束(首先是我脑海中浮现的东西)是一个随机数发生器)。

因此,您在数学问题中有一个特殊情况,您需要定义一个特殊值(无限个交点)。通常,这是我使用简单静态常量的地方。只需定义一些静态常量IEnumerable并对其进行测试,以确定您的算法是否具有“无限数量的交集”作为结果。

更具体地回答您当前的问题:不要再造成真正的堆栈溢出。这是您可以对代码用户做的最糟糕的事情。它无法被捕获并将立即终止您的进程(可能唯一的例外是当您在附加的仪器调试器中运行时)。

如果有的话,我会使用在其他地方使用的NotSupportedException来表示某些类不支持某个功能(例如ICollection可能会在Remove()中将其抛出是只读的)。

答案 2 :(得分:3)

如果我理解正确 - 无限在这里是一个令人困惑的词。我认为你需要一个 monad ,它可以是可枚举的。但是现在让我们坚持使用无限

我想不出在C#中实现这个的好方法。可以实现的所有方法都不与C#生成器集成。

使用C#generator,您只能发出有效值;所以没有办法表明这是一个无限可枚举。我不喜欢从生成器抛出异常的想法,以表明它是无限;因为要检查它是无限,您每次都必须尝试捕获

如果您不需要支持生成器,那么我会看到以下选项:

  1. 实施 sentinel 可枚举:

    public class InfiniteEnumerable<T>: IEnumerable<T> {
        private static InfiniteEnumerable<T> val;
    
        public static InfiniteEnumerable<T> Value {
            get {
                return val;
            }
        }
    
        public IEnumerator<T> GetEnumerator() {
            throw new InvalidOperationException(
                "This enumerable cannot be enumerated");
        }
    
        IEnumerator IEnumerable.GetEnumerator() {
            throw new InvalidOperationException(
                 "This enumerable cannot be enumerated");
        }
    }
    

    样本用法:

    IEnumerable<int> enumerable=GetEnumerable();
    
    if(enumerable==InfiniteEnumerable<int>.Value) {
        // This is 'infinite' enumerable.
    }
    else {
        // enumerate it here.
    }
    
  2. 实施Infinitable<T>包装器:

    public class Infinitable<T>: IEnumerable<T> {
        private IEnumerable<T> enumerable;
        private bool isInfinite;
    
        public Infinitable(IEnumerable<T> enumerable) {
            this.enumerable=enumerable;
            this.isInfinite=false;
        }
    
        public Infinitable() {
            this.isInfinite=true;
        }
    
        public bool IsInfinite {
            get {
                return isInfinite;
            }
        }
    
        public IEnumerator<T> GetEnumerator() {
            if(isInfinite) {
                throw new InvalidOperationException(
                    "The enumerable cannot be enumerated");
            }
    
            return this.enumerable.GetEnumerator();
        }
    
        IEnumerator IEnumerable.GetEnumerator() {
            if(isInfinite) {
                throw new InvalidOperationException(
                     "The enumerable cannot be enumerated");
            }
    
            return this.enumerable.GetEnumerator();
        }
    }
    

    样本用法:

    Infinitable<int> enumerable=GetEnumerable();
    
    if(enumerable.IsInfinite) {
        // This is 'infinite' enumerable.
    }
    else {
        // enumerate it here.
        foreach(var i in enumerable) {
        }
    }
    

答案 3 :(得分:2)

无限序列可以是完全可迭代/可枚举的。自然数是可枚举的,有理数或PI数也是可数的。无限是有限的,而不是可枚举的。

您提供的变体不代表无限序列。有无数多个不同的无限序列,你可以通过迭代它们看到它们是不同的。另一方面,你的想法是拥有一个违背这种多样性的单身人士。

如果你有一些无法枚举的东西(比如实数集),那么你就不应该把它定义为IEnumerable,因为它违反了合同。 如果你想辨别有限和无限可枚举序列,只需创建一个新接口IInfiniteEnumerable : IEnumerable并用它标记无限序列。

标记无限序列的接口

public interface IInfiniteEnumerable<T> : IEnumerable<T> {
}

使用C#的IEnumerable<T>语法可以轻松创建将现有IInfiniteEnumerable<T>转换为IEnumerableyield s的包装器,但我们需要将它们转换为{{1} })

IInfiniteEnumerable

一些无限感知例程(如计算序列长度)

public class InfiniteEnumerableWrapper<T> : IInfiniteEnumerable<T> {
    IEnumerable<T> _enumerable;

    public InfiniteEnumerableWrapper(IEnumerable<T> enumerable) {
        _enumerable = enumerable;
    }

    public IEnumerator<T> GetEnumerator() {
        return _enumerable.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return _enumerable.GetEnumerator();
    }
}

两个序列的例子 - 有限范围序列和无限斐波那契序列。

//TryGetCount() returns null if the sequence is infinite
public static class EnumerableExtensions {
    public static int? TryGetCount<T>(this IEnumerable<T> sequence) {
        if (sequence is IInfiniteEnumerable<T>) {
            return null;
        } else {
            return sequence.Count();
        }
    }
}

生成随机序列并尝试计算其长度的测试应用程序。

public class Sequences {
    public static IEnumerable<int> GetIntegerRange(int start, int count) {
        return Enumerable.Range(start, count);
    }

    public static IInfiniteEnumerable<int> GetFibonacciSequence() {
        return new InfiniteEnumerableWrapper<int>(GetFibonacciSequenceInternal());
    }

    static IEnumerable<int> GetFibonacciSequenceInternal() {
        var p = 0;
        var q = 1;
        while (true) {
            yield return p;
            var newQ = p + q;
            p = q;
            q = newQ;
        }
    }
}

程序输出如下:

public class TestApp {
    public static void Main() {
        for (int i = 0; i < 20; i++) {
            IEnumerable<int> sequence = GetRandomSequence();
            Console.WriteLine(sequence.TryGetCount() ?? double.PositiveInfinity);
        }
        Console.ReadLine();
    }

    static Random _rng = new Random();
    //Randomly generates an finite or infinite sequence
    public static IEnumerable<int> GetRandomSequence() {
        int random = _rng.Next(5) * 10;
        if (random == 0) {
            return Sequences.GetFibonacciSequence();
        } else {
            return Sequences.GetIntegerRange(0, random);
        }
    }
}