为什么IEnumerator<;T>;从IDisposable继承,而非泛型IEnumerator不继承

本文关键字:继承 IEnumerator 泛型 lt gt 为什么 IDisposable | 更新日期: 2023-09-27 17:48:53

我注意到通用IEnumerator<T>继承自IDisposable,但非通用接口IEnumerator没有。为什么要这样设计?

通常,我们使用foreach语句来遍历IEnumerator<T>实例。foreach生成的代码实际上有一个在finally中调用Dispose()的try-filly块。

为什么IEnumerator<;T>;从IDisposable继承,而非泛型IEnumerator不继承

基本上这是一个疏忽。在C#1.0中,foreach从不称为Dispose1。有了C#1.2(在VS2003中引入-奇怪的是没有1.1),foreach开始在finally块中检查迭代器是否实现了IDisposable-他们必须这样做,因为回顾性地使IEnumerator扩展IDisposable会破坏每个人对IEnumerator的实现。如果他们首先发现foreach处理迭代器是有用的,我相信IEnumerator会扩展IDisposable

当C#2.0和。NET 2.0的出现,然而,他们有了一个新的机会——新的接口,新的继承。让接口扩展IDisposable更有意义,这样就不需要在finally块中进行执行时间检查,现在编译器知道,如果迭代器是IEnumerator<T>,它可以发出对Dispose的无条件调用。

EDIT:在迭代结束时调用Dispose非常有用(不管它如何结束)。这意味着迭代器可以保留资源,这使得它可以逐行读取文件。迭代器块生成Dispose实现,确保与迭代器的"当前执行点"相关的任何finally块在被处理时都会被执行,因此您可以在迭代器中编写正常代码,并且应该适当地进行清理。


1回顾1.0规范,它已经被指定了。我还没有能够验证前面的声明,即1.0实现没有调用Dispose

IEnumerable<T> 不继承IDisposable。IE分子<T> 但是,确实继承了IDisposable,而非泛型IEnumerator则没有。即使对非泛型IEnumerable(返回IEnumerator)使用foreach,编译器仍然会生成IDisposable检查,并在枚举器实现接口时调用Dispose()。

我猜通用枚举器<T> 从IDisposable继承,因此不需要进行运行时类型检查——它可以继续调用Dispose(),这应该具有更好的性能,因为如果枚举器有一个空的Dispose(()方法,它可能会被优化掉。

我合理地编写了一个库,其中我使用了IEnumerable of T/IEnumerator of T,库的用户可以实现自定义迭代器——他们应该只实现IEnumerator of T

我发现T的IEnumerator会继承IDisposable,这很奇怪。如果我们想释放非托管资源,我们就实现IDisposable,对吗?因此,它只适用于实际拥有非托管资源的枚举器,如IO流等。如果有意义,为什么不让用户在其枚举器上同时实现T的IEnumerator和IDisposable呢?在我的书中,这违反了单一责任原则——为什么要混合枚举器逻辑和处理对象。

IEnumerable`继承IDisposing吗?根据。NET反射器或MSDN。你确定你没有把它和IEnumerator混淆吗?它使用IDisposing,因为它只用于枚举集合,而不意味着使用寿命。

除非你设法从AndersH本人或与他关系密切的人那里得到回应,否则很难确定这一点。

然而,我的猜测是,它与同时在C#中引入的"yield"关键字有关。如果您查看编译器在使用"yield return x"时生成的代码,您将看到该方法被封装在实现IEnumerator的辅助类中;让IEnumerator从IDisposable派生可以确保它可以在枚举完成时进行清理。

IIRC拥有IEnumerable<T>IEnumerable的全部原因是IEnumerable捕食的结果。Net的模板。我怀疑你的问题也是如此。