有没有一种方法可以确定IEnumerable<T>是一个序列生成器还是真正的集合?

4

让我提供一个例子:

假设我有一个方法:

public void DoStuff(IEnumerable<T> sequence)
{
    if (/* is lazy sequence */) throw ...

    // Do stuff...
}

我希望防止潜在的无限序列。

编辑:

为了详细说明,防止无限集合只是使用方法之一。就像Jon所提到的,你可以很容易地拥有无限的IList。这是一个好观点。

其他用途可能是检测数据是否可能不可重复。比如随机生成器。真实的集合已经在内存中具有数据,对其进行两次迭代将给出相同的数据。


1
那为什么不直接使用ICollection<T>呢?(注意:它仍然可以是无限的,也可以是惰性的,但它是一个“集合”)。 - jason
2
“真正的集合”需要什么要求?O(1)的随机访问?链表是否允许?File.ReadLines的结果是否是真正的集合? - dtb
2
理想情况下,你不需要这样的检查,因为你不使用任何需要有限总长度的操作。在你确实需要时,更好的方法可能是要求一个保证(或至少更有可能)是有限的不同类型。还要考虑到并非所有的延迟都需要无限制。 - user395760
@RonWarholic:确定一个惰性序列(特别是使用 yield return 的序列 - 请原谅,我不记得这种代码块的 C# 术语)是否有限似乎确实是停机问题。确定一个对象是否实际上是接口背后的(严格评估因此有限的)集合可能是可能的,尽管这可能需要绕过类型系统。 - user395760
3
你在说,如果有人手动编写状态机实现,那就可以,但如果编译器替你编写,那就不好了?我觉得这毫无意义。你真正想解决的问题是什么? - Eric Lippert
显示剩余3条评论
3个回答

5

没有什么是绝对保证的。你可以查看序列是否还实现了 IList<T> 接口,这将禁止迭代器块的实现,但仍然可以是一个“虚拟”列表,它将无限继续下去,而且对于一些有限的非迭代器块集合也会失败。

就你想要保护的内容而言,如果一个长度为20亿的序列对你的需求足够了吗?如果有一个扩展方法,当你得到超过某个“限制”时抛出异常(但是懒加载),这个方法适合你吗?(类似于 Take,但是在结尾处崩溃。)


5

这是不可能的。

IEnumerable API无法告诉你它是否无限。你可以尝试将其转换为ICollection,以捕获某人向您传递其中一个的常见情况,但如果这就是您想要的,那么为什么不首先使用ICollection<...>对象?


1

迭代器不支持IEnumerable<>.Reset()方法:

public void DoStuff(IEnumerable<T> sequence)
{
    sequence.Reset();
    // etc..
}

你会得到一个NotSupportedException异常,这是好的,它的消息是“指定的方法不受支持”,这是可以容忍的。

1
有趣但感觉很不专业 :-) - Kugel
3
“这是不可能的!”这个回答非常流行,也许你应该选择那个。 - Hans Passant
@Hans +1,因为它既尖刻又暗示真相。 - Shibumi
不明白为什么这被接受作答案。首先,IEnumerable<T>没有Reset()方法-只有IEnumerator<T>有。而且这些只是接口。我的实现可以做任何它想做的事情,包括返回一个无限序列,你可以将其重置到第一个元素(例如带有初始种子的随机数生成器)。所以,不,这是不可能的。 - Alastair Maw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接