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


77

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

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

Answers:


84

基本上,这是一个疏忽。在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>it,则可以发出对to的无条件调用Dispose

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


1回顾1.0规格,它已经被指定。我尚未能够验证1.0实施未调用的此较早的声明Dispose


我是否还必须期望IEnumerable.GetEnumerator(非泛型)的IDisposable表现呢?
Shimmy Weitzhandler,2012年

1
@Shimmy:必须接受接受非泛型的任意实现的代码,IEnumerable以确保GetEnumerator将处置从中返回的任何可抛弃对象。不应该认为是破损的代码。
超级猫

@supercat:自编写此答案以来,我发现它已经在1.0规范中。我没有设法进行1.0安装来检查我的行为是否正确。将进行编辑。
乔恩·斯基特

1
@JonSkeet:任何没有Dispose逻辑的C#版本foreachVisualBasic.Collection该类上的表现都非常差(这在很多方面都令人讨厌和古怪,但是-与那个时代的其他Microsoft集合不同-允许删除项目)在枚举期间)。该Collection班避免持有优秀统计员任何强引用,并清除它们,如果他们得到垃圾收集,但如果集合枚举GC周期,这些普查员之间多次不清理,就会得到非常缓慢。
2013年

@supercat:当然,这并不意味着它没有发生……;)这就是为什么我要验证它。
乔恩·斯基特

5

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

我猜想通用的Enumerator <T>是从IDisposable继承的,因此不需要运行时类型检查-它可以继续调用Dispose()来提高性能,因为如果枚举器有一个空的Dispose()方法。


如果编译器知道IEnumerableat编译,那么它可以优化对的调用Dispose,也可以优化对类型的检查。
爱德华·布雷

5

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

我发现T的IEnumerator继承自IDisposable很奇怪。如果我们想释放未管理的资源,我们实现IDisposable吗?因此,这仅与实际上拥有非托管资源(例如IO流等)的枚举器有关。为什么不让用户在其枚举器上同时实现T的IEnumerator和IDisposable呢?在我的书中,这违反了单一责任原则-为什么要混合枚举器逻辑和布置对象。


如果GetEnumerator返回需要清除的对象(例如,因为它正在从文件中读取将要关闭的数据行),则知道何时不再需要枚举数的实体必须具有某种方式将该信息传递给某个实体,该实体可以执行清理。 IDisposable对于Liskov替代原则,其行为是向后的,因为退回可能需要清理的东西的工厂不能安全地代替承诺退回不需要的东西的工厂,但是反向替换是安全的并且应该是合法的。
超级猫

我还发现IDisposableIEnumerator<T>是有点混乱,我发现它有助于检查出如何EnumeratorList<T>实现IDisposable在.NET源。请注意,Enumerator structDispose方法如何,但里面什么也没有。请注意,此IDisposable行为绝不意味着“ AList<Bitmap>应该将其Bitmap放入foreach
jrh


@supercat很好地利用了Liskov替换原理来修改打破接口隔离原理的方法;)
mireazma

@mireazma:如果容器类型的层次结构与对象实例类型的层次结构稍有不同,这将是有帮助的,其中,容器可以根据它们是专有引用,自身公开的引用,引用其他人拥有的对象还是引用来分类无主的对象。集合为诸如相等性检查之类的方法实现方法的方式应取决于这些区别,但是语言无法传达这些区别。
超级猫

0

IEnumerable`是否继承IDisposed?根据.NET反射器或MSDN。您确定不会将它与IEnumerator混淆吗?之所以使用IDisposed,是因为它仅用于枚举集合,而并不意味着寿命长。


正在摆姿势?另外,您的意思是“不符合...”吗?
彼得·里奇

2
我给您+1来抵消-1,因为您的消息是在原始发布者错误地指定IEnumerable和IEnumerator的时间之间发布的,并且很可能是促使OP解决他的问题的原因。但是,由于问题早已得到解决,因此您也可以删除“答案”,因为它肯定不再适用。
2013年

0

除非您设法从AndersH本人或与他亲近的人那里得到回应,否则很难确定这一点。

但是,我的猜测是,它与C#中同时引入的“ yield”关键字有关。如果查看使用“ yield return x”时由编译器生成的代码,您会看到该方法包装在实现IEnumerator的帮助器类中;将IEnumerator继承自IDisposable可以确保枚举完成后可以清除它。


yield只会使编译器为状态机生成代码,而无需在普通GC之外进行任何处理
Mark Cidade'Oct

@marxidad:完全不正确。考虑如果在迭代器块中出现“ using”语句,会发生什么情况。参见csharpindepth.com/Articles/Chapter6/…–
乔恩·斯基特

@Jon:并非完全不正确。尽管对于使用using的情况,IDisposable并不是严格必需的,但使所有新型枚举器都是可抛弃的,并为以防万一每次都调用Dispose()更为简单。
马克·西达德

我打算重新表达我的评论,使其比“完全错误”要柔和一些,但是声称编译器生成不需要任何处理的代码仍然是不准确的IMO。它有时的确,但你的毯子声明是不正确的。
乔恩·斯基特

VisualBasic.Collection类将表现不佳(几个数量级慢于正常),如果被创建并没有被设置的废弃许多枚举器。关联需要DisposeIEnumerable很明显这样的发布之前.net 1.0, but probably late enough that changing IEnumerable`继承IDisposable会影响发行计划。
2013年

-2

IIRC拥有IEnumerable<T>和的全部IEnumerable原因是IEnumerable.Net模板内容的抢占。我怀疑您的问题也是这样。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.