79

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

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

4

6 回答 6

87

基本上这是一个疏忽。在 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 块中进行执行时检查,现在编译器知道如果迭代器是 anIEnumerator<T>它可以发出对Dispose.

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


1回顾 1.0 规范,它已经被指定了。我还无法验证 1.0 实现没有调用Dispose.

于 2008-10-24T06:06:29.190 回答
5

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

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

于 2008-10-24T05:56:13.657 回答
5

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

我发现 T 的 IEnumerator 会从 IDisposable 继承,这很奇怪。如果我们想释放非托管资源,我们实现 IDisposable 对吗?所以它只与实际持有非托管资源的枚举器相关——比如 IO 流等。如果有意义的话,为什么不让用户在他们的枚举器上同时实现 T 的 IEnumerator 和 IDisposable 呢?在我的书中,这违反了单一责任原则——为什么要混合枚举器逻辑和处置对象。

于 2011-12-28T06:42:41.240 回答
0

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

于 2008-10-24T05:45:45.813 回答
0

对此有点难以确定,除非您设法从 AndersH 本人或与他关系密切的人那里得到回应。

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

于 2008-10-24T05:57:36.367 回答
-2

IIRC 关于拥有IEnumerable<T>并且IEnumerableIEnumerable早于 .Net 模板内容的结果。我怀疑你的问题是一样的。

于 2008-10-24T05:29:38.747 回答