我注意到泛型IEnumerator<T>
继承自IDisposable,但非泛型接口IEnumerator却没有。为什么这样设计?
通常,我们使用foreach语句来浏览IEnumerator<T>
实例。生成的foreach代码实际上有try-finally块,最终调用Dispose()。
答案 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的模板内容的结果。我怀疑你的问题是一样的。