根据Jon Skeet的一篇文章(http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx)所述,C#规范本身就是这样规定的。这是什么原因呢?
根据Jon Skeet的一篇文章(http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx)所述,C#规范本身就是这样规定的。这是什么原因呢?
在所有序列中都正确支持是不可能的;许多是仅一次(网络流等)。如果你不能始终依赖它,那么它就毫无用处,因为抽象已经被破坏了。当然,你可以有一个 IResettableEnumerator
,但是IEnumerator
上的Reset()
是不起作用的。坦率地说,这是一个错误(我个人认为)。
我怀疑这也会使迭代器块更加复杂(它们目前是编译器中最复杂的两个部分之一;我记不清哪个是“最复杂”的了,它们还是匿名方法/捕获变量)。
IEnumerator
都不支持IEnumerator.Reset()
方法。在第10.14.4节“枚举器对象”中指出:
…[枚举器对象]不支持
IEnumerator.Reset()
方法,调用此方法会引发System.NotSupportedException异常。
但是,该部分(和说明)特指“enumerator objects”,其定义为:
当使用迭代块实现返回枚举器接口类型的函数成员时,调用该函数成员不会立即执行迭代器块中的代码。 相反,将创建并返回一个枚举器对象。
换句话说,“enumerator object” 是由编译器生成的 IEnumerator
1。对于每个 IEnumerator
并没有限制,仅对从迭代块(称为 yield
)生成的那些有限制。
至于为什么?我猜测因为在一般情况下这几乎是不可能做到的--没有保存每个值及其带来的内存限制。再加上IEnumerator.Reset()
很少使用(你上次Reset一个enumerator是什么时候?),而且MSDN专门指出它不需要被实现:
Reset方法是为COM互操作性提供的。 不一定需要实现它; 相反,实现者可以只是抛出NotSupportedException异常。
这样做可以减少很多复杂性而不会引起任何人的注意。
关于要求它抛出异常,我认为这只是比让实现者决定更简单的方法。在我看来,要求抛出异常有点过分了 - 可能存在一些合理情况,编译器(或其他实现1)可以为其生成Reset方法,但我并不认为这是一个真正的问题。
1 从技术上讲,规范留下了其他实现的可能性:
枚举器对象通常是编译器生成的枚举器类的实例,该类封装了迭代器块中的代码并实现了枚举器接口,但也可以使用其他实现方法。
但我不知道是否有其他具体的实现。无论如何,为了符合规范,“枚举器对象”的其他实现也必须抛出NotSupportedException
。
2 吹毛求疵的角落:我认为即使是“要求”抛出异常,也可能存在一些争议。规范在不使用首选“MUST,SHOULD,MAY”措辞的情况下,留下了一些余地。我认为“cause”更多的是一种实现的说明 - 而不是一项要求。不过,我没有阅读整个规范,因此也许他们在其他地方对这些术语进行了更明确的定义或更详细的说明。
IEnumerator.Reset
存在的唯一原因是为了包装 COM 枚举器,例如IEnumVARIANT
,它通常具有Reset
(并且可能已经优化)。 - Pavel Minaev