我的图形库中有以下代码:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
此代码适用于 IEnumerator 的某些实例,但不适用于其他实例(.NET 的所有标准实现,以及具有产量返回的迭代器方法)。
这种行为是否未指定?
如果不是,正确的表示法是什么?将“当前”保留为上一个,还是设置为默认值(T)?
更新:
我探索了 IEnumerator 的一些集合实现、Linq 扩展以及类型化数组:fiddle。
根据我的理解,Linq 违反了 IEnumerable 的约定,因为它不会在枚举结束后抛出 Current ,而类型化数组也违反了 IEnumerable 的约定,因为它抛出 on IEnumerable<T>.Current,并且没有为 IEnumerable.Current 指定异常类型。
有人可以澄清一下,文档中所说的“未定义”是什么意思?是否包括抛出未指定的异常:
在以下任何条件下,Current 都是未定义的: 在创建枚举数之后,枚举数立即定位在集合中的第一个元素之前。在读取 Current 的值之前,必须调用 MoveNext 将枚举器前进到集合的第一个元素。最后一次调用 MoveNext 返回 false,这表明集合结束。由于集合中发生的更改(例如添加、修改或删除元素),枚举数会失效。Current 返回相同的对象,直到调用 MoveNext。MoveNext 将 Current 设置为下一个元素。
如果按照接口的约定返回,则的值Current未定义,如其文档中所述。你永远不应该依赖任何特定的行为。您根本不应该检查每当is的值。MoveNextfalseCurrentMoveNextfalse
如果
MoveNext传递到集合末尾,则枚举数位于集合中最后一个元素之后并MoveNext返回false。当枚举器位于此位置时,后续调用MoveNext也会返回false。如果最后一次调用MoveNextreturnedfalse,Current未定义。
(请注意,IEnumerator在Current这种情况下应该抛出异常,但根据我的经验,大多数实现都不会显式抛出异常,它们只是公开未定义的值;此更改反映在较新IEnumerator<T>接口的文档中。)