IEnumerator<T>
继承自IDisposable,但非通用接口IEnumerator没有。为什么会这样设计呢?通常,我们使用foreach语句遍历
IEnumerator<T>
实例。foreach生成的代码实际上有try-finally块,在finally中调用Dispose()方法。IEnumerator<T>
继承自IDisposable,但非通用接口IEnumerator没有。为什么会这样设计呢?IEnumerator<T>
实例。foreach生成的代码实际上有try-finally块,在finally中调用Dispose()方法。foreach
从未调用Dispose
1。随着C# 1.2(引入于VS2003 - 奇怪的是没有1.1),foreach
开始在finally
块中检查迭代器是否实现了IDisposable
- 他们不得不这样做,因为对IEnumerator
进行回顾性的扩展会破坏每个人的IEnumerator
实现。如果他们当初意识到让foreach
来处理迭代器释放资源很有用,我相信IEnumerator
就会扩展IDisposable
。
然而,当C# 2.0和.NET 2.0推出时,他们有了新的机会 - 新的接口,新的继承。将接口扩展IDisposable
更加合理,这样你就不需要在finally块中进行执行时检查,并且现在编译器知道如果迭代器是IEnumerator<T>
,它可以发出无条件调用Dispose
。
编辑:在迭代结束时调用Dispose
非常有用(无论如何它都会结束)。这意味着迭代器可以保持对资源的控制 - 这使得它可以逐行读取文件,例如。迭代器块会生成Dispose
实现,确保与迭代器的“当前执行点”相关联的任何finally
块在释放时都会执行 - 因此您可以在迭代器内编写普通代码,并且清理工作应该会恰当地进行。
1 回顾1.0规范,已经指定了。我还没有能够验证这个早期说法,即1.0实现没有调用Dispose
。
IEnumerable<T>不继承IDisposable接口。但是,IEnumerator<T>继承了IDisposable接口,而非泛型的IEnumerator没有。即使您使用foreach循环遍历非泛型的IEnumerable(它返回IEnumerator),编译器仍会生成IDisposable检查,并在枚举器实现该接口时调用Dispose()方法。
我猜想,泛型的Enumerator<T>继承了IDisposable接口,因此不需要运行时类型检查-它可以直接调用Dispose()方法,这应该具有更好的性能,因为如果枚举器具有空的Dispose()方法,则可以进行优化。
IEnumerable
,以便它可以优化调用 Dispose
的过程,那么它也可以优化类型检查。 - Edward Brey我最近编写了一个库,在其中使用了 IEnumerable of T
/ IEnumerator of T
,用户可以实现自定义迭代器,只需实现 IEnumerator of T
即可。
我发现 IEnumerator of T 继承了 IDisposable,这让我觉得非常奇怪。我们只有想要释放非托管资源时才会实现 IDisposable,对吧?那么只有实际持有非托管资源的枚举器(如 IO 流等)才相关。如果有意义,为什么不让用户在其枚举器上同时实现 IEnumerator of T 和 IDisposable 呢?在我看来,这违反了单一责任原则 - 为什么要混淆枚举逻辑和对象销毁呢。
GetEnumerator
返回需要清理的对象(例如因为它正在从文件中读取数据行,这些数据行必须关闭),则了解枚举器何时不再需要的实体必须有某种方式将该信息传达给能够执行清理的实体。IDisposable
在Liskov替换原则方面表现出相反的行为,因为返回可能需要清理的内容的工厂不能安全地替换承诺返回不需要清理的内容的工厂,但是相反的替换是安全的,应该是合法的。 - supercatIEnumerator<T>
上使用IDisposable
有点困惑,查看一下.NET源代码中List<T>
的Enumerator
如何实现IDisposable
会很有帮助。请注意,Enumerator
结构体具有一个Dispose
方法,但其中没有任何内容。请注意,这种IDisposable
行为绝不意味着“List<Bitmap>
应该在foreach
中释放其Bitmap
!” - jrh除非你能得到AndersH本人或他身边的人的回应,否则很难确定这个问题。
然而,我的猜测是它与在C#中引入的“yield”关键字有关。如果你查看编译器在使用“yield return x”时生成的代码,你会看到该方法被包装在一个实现IEnumerator的帮助类中;让IEnumerator从IDisposable继承确保它可以在枚举完成时进行清理。
VisualBasic.Collection
类的性能将会非常低(比正常情况下慢几个数量级)。因此,在发布 .net 1.0 之前就已经明显需要将 Dispose
与 IEnumerable
关联起来,但可能太晚了,将 IEnumerable
继承到 IDisposable
可能会影响发布计划。 - supercat如果我没记错,关于有 IEnumerable<T>
和 IEnumerable
的整件事情是由于 IEnumerable
比 .Net 的模板还要早。我怀疑你的问题也是同样的道理。
IEnumerable.GetEnumerator
也是IDisposable
吗? - Shimmy WeitzhandlerIEnumerable
任意实现的代码有责任确保从GetEnumerator
返回的任何可处理对象都将被处理。不这样做的代码应被视为不完整。 - supercatDispose
逻辑,则其与VisualBasic.Collection
类(该类在许多方面都很烦人和古怪,但与当时的其他 Microsoft 集合不同,允许在枚举期间删除项)会表现得非常糟糕。Collection
类避免保留对未完成枚举器的任何强引用,并将在它们被垃圾回收时清理它们,但是如果集合在 GC 循环之间被枚举多次并且这些枚举器没有被清理,则会变得非常缓慢。 - supercat