16

代码更新

为了修复过滤的错误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被声明为常数。


  • 概括

    我正在尝试实现一个无限的 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 2or option 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

4 回答 4

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

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

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

于 2013-05-23T11:37:08.407 回答
4

正如您在链接的另一篇文章中提到的那样,无限枚举对于 C# 进行枚举非常有意义,并且有大量现实世界的示例,人们编写永远不会结束的枚举器(我脑海中浮现的第一件事是随机数发生器)。

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

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

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

于 2013-05-30T10:32:44.840 回答
3

如果我理解正确 -无限在这里是一个令人困惑的词。我认为您需要一个可枚举或不可枚举的单子。但现在让我们坚持使用无限

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

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

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

  1. 实现哨兵可枚举:

    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) {
        }
    }
    
于 2013-05-25T16:29:10.477 回答
2

无限序列可能是完全可迭代/可枚举的。自然数是可枚举的,有理数或 PI 数字也是如此。无限是有限的对立面,不可枚举。

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

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

标记无限序列的接口

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

将现有转换IEnumerable<T>IInfiniteEnumerable<T>( IEnumerables 很容易使用 C#yield语法创建的包装器,但我们需要将它们转换为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);
        }
    }
}

程序输出如下:

20
40
20
10
20
10
20
Infinity
40
30
40
Infinity
Infinity
40
40
30
20
30
40
30
于 2013-05-31T00:33:29.213 回答