迭代器块的编译器实现有误?

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只是一个有后备字段支持的属性,它总是返回该字段的值而没有其他内容。也许这是某种形式的优化?尽管如此,这看起来像是违反了契约。


哦,我错过了!显然它被改成了“未定义”。我想这回答了我的问题,但我不能接受一个评论,所以请把它变成一个答案。 - Ohad Schneider
你可以回答自己的问题。 - Trevor Elliott
1个回答

4
虽然您在IEnumerator.Current文档中是正确的,但IEnumerator<T>.Current's documentation则说明了在这种情况下该属性是未定义的。对于您的迭代器,它返回“2”。List<T>的枚举器返回default(T),而T[]的则会抛出异常。这些都是有效的实现,因为它们是未定义的。

以下任何条件下都未定义Current

  • 枚举器定位在集合中第一元素之前,即在枚举器创建之后。在读取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)。)

+1 但我们回到了起点,不是吗?IEnumerator 没有正确实现,也许我应该在 Connect 上报告一个 bug? - Ohad Schneider
@OhadSchneider 是的,你说得对。这只是一个小错误,但它确实是一个错误。 - Tim S.
我在这里提交了一个错误报告:https://connect.microsoft.com/VisualStudio/feedback/details/806924/iterator-blocks-are-not-implemented-according-to-documentation - Ohad Schneider

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