C# 为什么IEnumerator.Current的错误处理与IEnumerator不同<;T>;。现在的

C# 为什么IEnumerator.Current的错误处理与IEnumerator不同<;T>;。现在的,c#,ienumerable,C#,Ienumerable,我本以为为实现IEnumerable的空集合执行以下代码会引发异常: var enumerator = collection.GetEnumerator(); enumerator.MoveNext(); var type = enumerator.Current.GetType(); // Surely should throw? 因为集合是空的,所以访问IEnumerator.Current是无效的,我希望出现异常。但是,列表不会引发异常 这是允许的,表示在以下任何条件下,Current未

我本以为为实现
IEnumerable
的空集合执行以下代码会引发异常:

var enumerator = collection.GetEnumerator();
enumerator.MoveNext();
var type = enumerator.Current.GetType(); // Surely should throw?
因为集合是空的,所以访问
IEnumerator.Current
是无效的,我希望出现异常。但是,
列表
不会引发异常

这是允许的,表示在以下任何条件下,
Current
未定义:

  • 枚举数位于集合中第一个元素之前,紧随创建枚举数之后。在读取Current的值之前,必须调用MoveNext将枚举数前进到集合的第一个元素
  • 对MoveNext的最后一次调用返回false,这表示 收藏
  • 由于在集合中所做的更改(例如添加、修改或删除元素),枚举数无效
(我假设“未能抛出异常”可以归类为“未定义的行为”…)

但是,如果您执行相同的操作,但改用
IEnumerable
,则会出现异常。该行为由指定,其中规定:

  • 如果对MoveNext的最后一次调用返回false(表示集合结束),Current应引发InvalidOperationException
我的问题是:为什么会有这种差异?是否有我不知道的好的技术原因?

这意味着看似相同的代码可能表现得非常不同,这取决于它是使用
IEnumerable
还是
IEnumerable
,如下程序所示(请注意
showElementType1()
showElementType1()
中的代码是如何相同的):

使用系统;
使用系统集合;
使用System.Collections.Generic;
命名空间控制台应用程序2
{
班级计划
{
公共静态void Main()
{
var list=新列表();
showElementType1(列表);//不引发异常。
showElementType2(列表);//引发异常。
}
私有静态void showElementType1(IEnumerable集合)
{
var枚举器=collection.GetEnumerator();
枚举数。MoveNext();
var type=enumerator.Current.GetType();//此处未引发异常。
控制台写入线(类型);
}
私有静态void showElementType2(IEnumerable集合)
{
var枚举器=collection.GetEnumerator();
枚举数。MoveNext();
var type=enumerator.Current.GetType();//此处引发InvalidOperationException。
控制台写入线(类型);
}
}
}

IEnumerable的问题在于
当前的
类型为
T
。而不是抛出异常(它是从
MoveNextRare
设置的)

使用
IEnumerable
时,您没有该类型,并且无法返回默认值

实际问题是您没有检查
MoveNext
的返回值。如果返回
false
,则不应调用
Current
。例外情况是可以的。我认为他们发现在
IEnumerable
案例中返回
default(T)
更方便

异常处理会带来开销,返回的
default(T)
没有(那么多)。可能他们只是认为在
IEnumerable
的情况下,从
Current
属性返回没有什么用处(他们不知道类型)。使用
default(T)
时,该问题在
IEnumerable
中“解决”

据此(感谢评论):

出于性能原因,生成的枚举数的Current属性保持非常简单—它只返回生成的“Current”支持字段的值

这可能指向异常处理开销的方向。或验证
当前值所需的额外步骤

他们实际上只是将责任推给了
foreach
,因为这是枚举器的主要用户:

绝大多数与枚举数的交互都是以foreach循环的形式进行的,foreach循环已经防止访问这些状态中的任何一种状态的电流,因此在每次迭代中消耗额外的CPU周期来检查几乎没有人会遇到的这些状态是浪费的


更好地匹配人们在实践中如何实现它。同样,将以前版本的文档中的“当前也引发异常…”更改为当前版本中的“当前应引发…”

根据实现的工作方式,抛出异常可能是一项相当大的工作,但是由于
Current
MoveNext()
结合使用,因此很少会出现异常状态。当我们认为绝大多数的使用都是编译器生成的,并且实际上没有bug的范围,在代码< > MOVENTXEL()/代码>之前,或者在它返回了代码> false < /代码>之后,调用一个bug。在正常使用情况下,我们可以预期这种情况永远不会出现

因此,如果您正在编写
IEnumerable
IEnumerable
的实现,而捕获错误条件非常困难,您可能会决定不这样做。如果你做了这个决定,它可能不会给你带来任何问题。是的,你违反了规则,但可能没关系

而且,由于它不会导致任何问题,除了以错误的方式使用接口的人之外,因此将其记录为未定义的行为会将负担从实现者转移到调用者身上,使其不做调用者应该做的事情
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);
        }
    }
}