我的图形库中有以下代码:
while(true) {
using (var i = ancestry.GetAdjacent(current).GetEnumerator())
{
if (!i.MoveNext())
yield break;
if (i.MoveNext())
throw new InvalidOperationException("ancestry graph can only have one adjacent vertex for any given vertex");
// it's a bug, as some enumerators could return null after failed MoveNext().
current = i.Current;
if (isSentinel(current))
yield break;
}
}
此代码适用于 IEnumerator 的某些实例,但不适用于其他实例(.NET 的所有标准实现,以及具有产量返回的迭代器方法。
该行为是否未指定?
如果不是,正确的表示法是什么?将“当前”保留为上一个,还是设置为默认值(T)?
更新:
我探索了 IEnumerator 对于集合、Linq 扩展以及类型化数组的一些实现:fiddle。
在我的理解中,Linq 违反了 IEnumerable 的约定,因为它不会在枚举结束之后在 Current 上抛出异常,而类型化数组也违反了 IEnumerable 的约定,因为它在
IEnumerable<T>.Current
上抛出,并且没有为 指定异常类型IEnumerable.Current.
有人可以澄清一下,文档中所说的“未定义”是什么意思?是否包括抛出未指定的异常:
来自 IEnumerator.Current Property 文档:
在以下任何条件下,当前未定义:
- 在创建枚举器之后,枚举器立即定位在集合中第一个元素之前。在读取 Current 的值之前,必须调用 MoveNext 将枚举器前进到集合的第一个元素。
- 最后一次调用 MoveNext 返回 false,表示集合结束。
- 由于集合中发生的更改(例如添加、修改或删除元素),枚举数会失效。 Current 返回相同的对象,直到调用 MoveNext。 MoveNext 将 Current 设置为下一个元素。
如果
Current
返回 MoveNext
,则 false
的值未定义,按照接口合同,如其文档中所述。 你永远不应该依赖任何特定的行为。 当 Current
为 MoveNext
时,您根本不应该检查 false
的值。
如果
到达了集合的末尾,则枚举数位于集合中最后一个元素之后,并且MoveNext
返回MoveNext
。当枚举器位于此位置时,对false
的后续调用也会返回MoveNext
。如果最后一次调用false
返回MoveNext
,则false
未定义。Current
(请注意
IEnumerator
表示 Current
在这种情况下应该抛出异常,但根据我的经验,大多数实现都不会显式抛出异常,它们只是公开未定义的值;此更改反映在较新的 IEnumerator<T>
中)
接口文档。)