代码已更新
要修复已过滤的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
。
一旦枚举,就抛出InvalidOperationException
。
public IEnumerator<T> GetEnumerator() {
for(var message="Attempted to enumerate an infinite enumerable"; ; )
throw new InvalidOperationException(message);
}
和3.在技术上是等效的,当真的溢出时,让堆栈溢出。
public IEnumerator<T> GetEnumerator() {
foreach(var x in this)
yield return x;
}
public IEnumerator<T> GetEnumerator() {
return this.GetEnumerator();
}
(描述于2)
不要等到它发生,直接抛出StackOverflowException
。
public IEnumerator<T> GetEnumerator() {
throw new StackOverflowException("... ");
}
棘手的事情是:
如果应用option 1
,即枚举此枚举,则变为无效操作。说这个灯不是用来照亮(虽然在我的情况下这是真的)并不奇怪。
如果应用option 2
或option 3
,即我们计划堆栈溢出。是否真的像标题一样,只是当stackoverflow是公平合理的时?完全合乎逻辑且合理吗?
最后一个选择是option 4
。然而,堆栈实际上并没有真正溢出,因为我们通过抛出假 StackOverflowException
来阻止它。这让我想起当汤姆克鲁斯扮演的约翰安德顿说:“但它并没有下降。你抓住了它。你阻止它发生的事实并没有改变它将要发生的事实。“
避免不合逻辑问题的一些好方法?
代码是可编译且可测试的,请注意在编译之前定义OPTION_1
到OPTION_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;
}
}
答案 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,您只能发出有效值;所以没有办法表明这是一个无限可枚举。我不喜欢从生成器抛出异常的想法,以表明它是无限;因为要检查它是无限,您每次都必须尝试捕获。
如果您不需要支持生成器,那么我会看到以下选项:
实施 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.
}
实施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>
转换为IEnumerable
(yield
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);
}
}
}