为什么IEnumerator.Current的错误处理与IEnumerator <T> .Current不同?

Mat*_*son 26 c# ienumerable

我原本以为为实现的空集合执行以下代码IEnumerable<T>会抛出异常:

var enumerator = collection.GetEnumerator();
enumerator.MoveNext();
var type = enumerator.Current.GetType(); // Surely should throw?
Run Code Online (Sandbox Code Playgroud)

因为集合是空的,所以访问IEnumerator.Current无效,我本来期望一个例外.但是,没有例外List<T>.

这是允许的文档IEnumerator<T>.Current,其中指出Current在以下任何条件下未定义:

  • 枚举器在创建枚举数之后立即定位在集合中的第一个元素之前.在读取Current的值之前,必须调用MoveNext以将枚举数推进到集合的第一个元素.
  • 对MoveNext的最后一次调用返回false,表示集合的结束.
  • 由于集合中所做的更改(例如添加,修改或删除元素),枚举器无效.

(我假设"未能抛出异常"可归类为"未定义行为"......)

但是,如果你做同样的事情,但使用一个IEnumerable,你会得到一个例外.此行为由文档IEnumerator.Current指定,其中指出:

  • 如果对MoveNext的最后一次调用返回false,则当前应该抛出InvalidOperationException,这表示集合的结束.

我的问题是:为什么会有这种差异?我不知道有一个很好的技术原因吗?

这意味着看起来相同的代码可以表现得非常不同,具体取决于它是否正在使用,IEnumerable<T>或者IEnumerable如下面的程序所示(注意代码内部showElementType1()showElementType1()相同):

using System;
using System.Collections;
using System.Collections.Generic;

namespace ConsoleApplication2
{
    class Program
    {
        public static void Main()
        {
            var list = new List<int>();

            showElementType1(list); // Does not throw an exception.
            showElementType2(list); // Throws an exception.
        }

        private static void showElementType1(IEnumerable<int> collection)
        {
            var enumerator = collection.GetEnumerator();
            enumerator.MoveNext();
            var type = enumerator.Current.GetType(); // No exception thrown here.
            Console.WriteLine(type);
        }

        private static void showElementType2(IEnumerable collection)
        {
            var enumerator = collection.GetEnumerator();
            enumerator.MoveNext();
            var type = enumerator.Current.GetType(); // InvalidOperationException thrown here.
            Console.WriteLine(type);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Pat*_*man 22

问题IEnumerable<T>在于Current类型T.而不是抛出一个异常,default(T)则返回(它是从设置MoveNextRare).

使用IEnumerable时没有类型,也无法返回默认值.

实际问题是你没有检查返回值MoveNext.如果它返回false,你不应该打电话Current.例外是可以的.我认为,他们发现更方便地返回default(T)IEnumerable<T>情况.

异常处理带来开销,返回default(T)不会(那么多).也许他们只是认为CurrentIEnumerable(他们不知道类型)的情况下从房产返回没有任何用处.这个问题在IEnumerable<T>使用时"解决"了default(T).

根据这个错误报告(感谢Jesse评论):

出于性能原因,生成的枚举器的Current属性保持非常简单 - 它只返回生成的"当前"支持字段的值.

这可能指向异常处理的开销方向.或者验证值的必要额外步骤current.

他们实际上只是挥动责任foreach,因为这是调查员的主要用户:

浩瀚广大普查员的相互作用是foreach循环它已经警惕在这两种状态的访问当前的形式,所以这将是浪费的燃烧多余的CPU周期,每次迭代检查,几乎没有人会遇到这些状态.

  • 我不认为返回`default(T)`是更方便的答案.当然,抛出异常同样容易(并且对调用者提供更多信息)?我的问题是,为什么他们会选择掩盖错误? (2认同)
  • 请参阅[此错误报告](https://connect.microsoft.com/VisualStudio/feedback/details/806924/iterator-blocks-are-not-implemented-according-to-documentation):`出于性能原因,当前属性为生成的枚举器非常简单 - 它只返回生成的"当前"后备字段的值. (2认同)
  • 但不抛出异常意味着无效代码将被错误地执行! (2认同)
  • @MatthewWatson:但如果有记录,你应该检查返回值,谁错了? (2认同)