【问题标题】:Why does IEnumerator<T> inherit from IDisposable while the non-generic IEnumerator does not?为什么 IEnumerator<T> 继承自 IDisposable 而非泛型 IEnumerator 不继承?
【发布时间】:2010-09-18 22:54:14
【问题描述】:

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

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

【问题讨论】:

    标签: c# generics ienumerator


    【解决方案1】:

    基本上这是一个疏忽。在 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&lt;T&gt;,它可以发出无条件调用到Dispose

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


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

    【讨论】:

    • 我是否必须期望IEnumerable.GetEnumerator(非通用)也成为IDisposable
    • @Shimmy:接受非泛型IEnumerable 的任意实现的代码有义务确保从GetEnumerator 返回的任何一次性对象都将被处置。不应被视为已损坏的代码。
    • @supercat:自从写了这个答案,我发现它已经在 1.0 规范中了。我还没有设法安装 1.0 来检查我的行为是否真的正确。将编辑。
    • @JonSkeet:在foreach 中没有Dispose 逻辑的任何C# 版本在VisualBasic.Collection 类中的表现都会很差(这在很多方面都很烦人和古怪,但是-- 与那个时代的任何其他 Microsoft 集合不同 -- 允许在枚举期间删除项目)。 Collection 类避免持有对未完成枚举器的任何强引用,并且如果它们被垃圾收集,则会清理它们,但是如果在 GC 周期之间枚举了多次集合并且这些枚举器没有被清理,它将得到 非常慢。
    • @supercat:这并不意味着它没有发生,当然...... ;) 这就是我想要验证它的原因。
    【解决方案2】:

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

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

    【讨论】:

    • 如果编译器在编译时知道IEnumerable,这样它就可以优化掉对Dispose的调用,它也可以优化掉类型检查。
    【解决方案3】:

    我用IEnumerable of T / IEnumerator of T 编写了一个库,库的用户可以实现他们应该只实现IEnumerator of T 的自定义迭代器。

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

    【讨论】:

    • 如果GetEnumerator返回一个需要清理的对象(例如,因为它正在从一个必须关闭的文件中读取数据行),一个知道何时不再需要枚举器的实体必须具有将该信息传达给可以执行清理的某个实体的某种方式。 IDisposable 在 Liskov 替换原则方面表现落后,因为返回可能需要清理的东西的工厂不能安全地替换承诺返回不需要的东西的工厂,但是反向替换是安全的并且 应该合法。
    • 我还发现IEnumerator&lt;T&gt; 上的IDisposable 有点令人困惑,我发现查看.NET 源代码中的Enumerator of List&lt;T&gt; implemented IDisposable 很有帮助。请注意 Enumerator struct 有一个 Dispose 方法,但其中没有任何内容。 请注意,这种IDisposable 行为绝不意味着“A List&lt;Bitmap&gt; 应将其Bitmaps 处置在foreach
    • 相关:IEnumerator: Is it normal to have an empty Dispose method?(答案:是的,是的)
    • @supercat 很好地利用 Liskov 替换原则来修改接口隔离原则的破坏;)
    • @mireazma:如果容器类型的层次结构与对象实例类型略有不同,这将很有帮助,其中容器可以根据它们是否拥有独占引用、是否拥有公开引用、引用他人拥有的对象,或指无主的对象。集合应该实现诸如相等性检查之类的方法的方式应该取决于这些区别,但是语言无法传达它们。
    【解决方案4】:

    IEnumerable` 是否继承了 IDisposing?根据 .NET 反射器或MSDN。你确定你没有把它和IEnumerator混淆吗?使用 IDisposing 是因为它仅用于枚举集合,并不意味着长寿。

    【讨论】:

    • IDisposing?另外,您的意思是“不根据...”吗?
    • 我给了你一个 +1 来抵消 -1,因为你的消息是在原始发布者错误地指定 IEnumerable 和 IEnumerator 之间发布的,并且很可能是促使 OP 解决他的问题的原因。尽管如此,由于问题早已解决,您不妨删除您的“答案”,因为它肯定不再适用。
    【解决方案5】:

    这有点难以确定,除非你设法从 AndersH 本人或他身边的人那里得到回应。

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

    【讨论】:

    • yield 只是让编译器为状态机生成代码,在正常 GC 之外不需要任何处理
    • @marxidad:完全不正确。考虑如果“使用”语句出现在迭代器块中会发生什么。见csharpindepth.com/Articles/Chapter6/…
    • @Jon:不完全不正确。尽管对于不使用 的情况,IDisposable 并不是绝对必要的,但为了以防万一,只需将所有新型枚举器设为一次性并每次调用 Dispose() 会更简单。
    • 我正要改写我的评论,使其比“完全不正确”更柔和一点,但声称编译器生成的代码不需要任何处理仍然是不准确的 IMO。 有时确实如此,但您的概括性陈述不正确。
    • VisualBasic.Collection 类的性能会很差(比正常慢几个数量级),如果创建并放弃了许多枚举器而不被处置。因此,在发布 .net 1.0, but probably late enough that changing IEnumerable` 以继承 IDisposable 之前,需要将 DisposeIEnumerable 关联起来会影响发布计划。
    【解决方案6】:

    IIRC 拥有IEnumerable&lt;T&gt;IEnumerable 的全部原因是IEnumerable 早于.Net 的模板内容。我怀疑你的问题是一样的。

    【讨论】:

      猜你喜欢
      • 2013-01-11
      • 2011-03-05
      • 1970-01-01
      • 2018-08-19
      • 2013-02-06
      • 2023-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多