4

考虑IEnumerator.Current的文档:

如果最后一次调用 MoveNext 返回 false,Current 也会引发异常,这表示集合结束

但是,迭代器块不会发生这种情况。例如:

void Main()
{
    using (var enumerator = GetCounter().GetEnumerator())
    {
        for (int i = 0; i < 10; i++)
        {
            enumerator.MoveNext();
            Console.WriteLine (enumerator.Current);
        }
    }
}

static IEnumerable<int> GetCounter()
{
   for (int count = 0; count < 3; count++)
   {
       yield return count;
   }
}

只会打印 8 次2,不会抛出异常。查看编译器转换Current它只是一个字段支持的属性,它始终返回字段的值,仅此而已。也许这是某种形式的优化?尽管如此,这看起来还是违反了合同。

4

1 回答 1

4

虽然您对IEnumerator.Current' 的文档是正确的,但IEnumerator<T>.Current' 的文档指出该属性对于这种情况是未定义的。使用您的迭代器,它返回“2”。List<T>' 的枚举器返回default(T),并且T[]' 抛出异常。这些都是有效的实现,因为它是未定义的。

在以下任何情况下,电流都是未定义的:

  • 枚举器位于集合中的第一个元素之前,紧接在创建枚举器之后。在读取 Current 的值之前,必须调用 MoveNext 将枚举器推进到集合的第一个元素。
  • 对 MoveNext 的最后一次调用返回false,表示集合结束。
  • 枚举器因集合中的更改而失效,例如添加、修改或删除元素。

值得注意的是,即使它实现了接口,从 生成的代码yield return也没有正确实现,因为在这种情况下IEnumerator它会继续返回:2

IEnumerator enumerator = GetCounter().GetEnumerator();
for (int i = 0; i < 10; i++)
{
    enumerator.MoveNext();
    Console.WriteLine (enumerator.Current);
}

(为了比较,List<T>它是否正确:如果你IEnumerator.Current在结束后得到,它会抛出异常,如果你IEnumerator<T>.Current在结束后调用,它会返回default(T)

于 2013-10-28T19:57:17.680 回答