为什么IEnumerator <t>从IDisposable继承而非通用IEnumerator不继承?</t>

时间:2008-10-24 05:27:35

标签: c# generics ienumerator

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

通常,我们使用foreach语句来浏览IEnumerator<T>实例。生成的foreach代码实际上有try-finally块,最终调用Dispose()。

6 个答案:

答案 0 :(得分:78)

基本上这是一个疏忽。在C#1.0中,foreach 从不调用Dispose 1 。使用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的无条件调用。

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


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

答案 1 :(得分:5)

的IEnumerable&LT; T&GT;不继承IDisposable。的IEnumerator&LT; T&GT;然而,继承IDisposable,而非通用IEnumerator没有。即使将 foreach 用于非通用IEnumerable(返回IEnumerator),编译器仍会生成对IDisposable的检查,如果枚举器实现了接口,则调用Dispose()。

我猜通用的枚举器&lt; T&gt;继承自IDisposable所以不需要运行时类型检查 - 它可以继续调用Dispose(),它应该有更好的性能,因为如果枚举器有一个空的Dispose()方法,它可能会被优化掉

答案 2 :(得分:4)

我知道这是一个古老的讨论,但我真的写了一个库,我使用了T的T / IEnumerator,其中库的用户可以实现自定义迭代器,他们应该只实现T的IEnumerator。

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

答案 3 :(得分:0)

IEnumerable`是否继承IDisposing?根据.NET反射器或MSDN。您确定不会将其与IEnumerator混淆吗?它使用IDisposing,因为它只用于枚举集合而不是长寿。

答案 4 :(得分:0)

有点难以确定,除非你设法得到AndersH本人或与他亲近的人的回复。

但是,我的猜测是它与C#中同时引入的“yield”关键字有关。如果你在使用“yield return x”时查看编译器生成的代码,你会看到该方法包含在一个实现IEnumerator的helper类中;让IEnumerator从IDisposable下降,确保它在枚举完成时可以清理。

答案 5 :(得分:-2)

IIRC关于IEnumerable<T>IEnumerable的全部内容是IEnumerable早于.Net的模板内容的结果。我怀疑你的问题是一样的。