【问题标题】:Should IDisposable be applied cascadingly?IDisposable 应该级联应用吗?
【发布时间】:2009-09-22 17:50:55
【问题描述】:

这是一个相当基本的问题,但我仍然有点挣扎。

当您希望对象的用户在最终被垃圾回收之前释放底层资源(例如套接字等)时,实现了 IDisposable。

当我有一个拥有 DbConnection 的类(实现 IDisposable)时,我的类是否也需要实现 IDisposable 并将调用链接到 DbConnection 或它拥有的任何其他 IDisposable 对象?否则 DbConnections 资源只会在我的类被 GarbageCollected 时释放,从而丢弃它对连接的引用,GC 将最终确定 DbConnection。

【问题讨论】:

    标签: c# .net idisposable


    【解决方案1】:

    是的,如果您控制一次性对象,您总是实施 IDisposable。 总是。如果你不这样做,你的代码不会中断,但如果你不这样做,它就违背了拥有一次性对象的目的。

    GC优化的一般规则是:

    • 任何控制不受 GC 管理的对象的类都必须实现终结器(通常也应实现 IDisposable)。这就是“顶级”一次性类通常的来源——它们通常控制窗口、套接字、互斥锁或您拥有的任何东西的句柄。
    • 任何实例化 IDisposable 成员的类都应实现 IDisposable 本身,并正确地 Dispose() 其组成部分。
    • 任何实例化 IDisposeable 对象的函数都应在使用完毕后正确地对其进行 Dispose()。不要让它超出范围。

    如果您是为自己编写应用程序,这些规则可能会被扭曲或忽略,但在将代码分发给他人时,您应该专业并遵守规则。

    这里的逻辑是,当您在 GC 视图之外控制内存时,GC 引擎无法正确管理您的内存使用情况。例如,在您的 .NET 堆上,您可能只有一个 4 字节的指针,但在非托管区域中,您可能会指向 200 MB 的内存。 GC 引擎在你有几十个之前不会尝试收集这些,因为它看到的只是几个字节;而在现实世界中,它看起来很像内存泄漏。

    因此,规则是,非托管内存应在您使用完毕后立即释放(IDisposable 链会为您执行此操作),而托管内存则在 GC 引擎使用时释放。

    【讨论】:

    • DataSet.Dispose() 不关心 Dispose() 它的 DataTables 是不是很好奇?
    • 这确实应该由语言更好地处理 - 必须在整个类层次结构中链接 IDisposable/Dispose 是荒谬和乏味的。
    【解决方案2】:

    是的,如果你的类需要处理它使用的任何对象,它需要是 IDisposable。一个例子是 StreamReader。它实现了 IDisposable,因此它可以释放其关联的流对象。

    【讨论】:

    • 问题是它是否确实需要处理任何对象。它可以。您如何看待 DbConnection 示例。
    • @Johannes:你的班级就是一个例子。它应该实现 IDisposable 以便它可以释放其关联的 DbConnnection 对象。
    • (是的,我故意转述大卫所说的话)
    【解决方案3】:

    如果我正确理解了您的问题,那么您有一个使用 DbConnection 的类。您希望确保在完成使用 DbConnection 或处置您的类时正确处置 DbConnection。有几种方法可以实现这一点。

    如果您在方法中使用数据库连接作为局部变量,则可以利用 using() {} 语句。

    using (SqlConnection sqlConnection = new SqlConnection(connStr))
    {
    ...do stuff with connection here
    }

    using () {} 语句自动对 () 中声明的对象调用 Dispose()。 (还要求()中声明的对象实现IDisposable,保证可以被disposable)

    如果您将 DbConnection 作为在对象构造期间初始化的私有变量或其他一些初始化方法使用,那么您可能希望自己实现 IDisposable,然后在 Dispose() 方法中调用 _dbConnection.Dispose()。这样,当您的对象被释放时,数据库连接对象也将被释放。

    public class MyDALObj : IDisposable
    {

    public MyDalObj()
    {
    ... create _dbConn object ...
    }

    public void Dispose()
    {
    _dbConn.Dispose();
    }

    private DbConnection _dbConn;
    }

    【讨论】:

      【解决方案4】:

      您应该这样做,因为这是您班级的用户确保正确处置内部持有的资源的唯一方法。

      但是,用于 Dispose() 的模式可能与通常编写的模式略有不同,在这种情况下,因为您不必区分非托管资源和托管资源(您的封装资源始终被视为“托管资源”) “资源)。

      我写了一篇关于这个特定主题的详细博客文章 - Encapsulating IDisposable Resources

      【讨论】:

        【解决方案5】:

        有两种不同的场景:

        1. 您的对象是给定一个要使用的对象引用,通过构造函数参数或属性,并且此对象实现 IDisposable。
        2. 您的对象构造了一个实现 IDisposable 的对象的实例。

        在第二种情况下,你的对象负责所涉及的资源,所以你的对象必须实现 IDisposable,并且当被释放时,你应该释放你构造的对象。

        您的 DbConnection 属于第二种情况,所以是的,您的对象应该实现 IDisposable,然后释放连接。

        在第一种情况下,您需要决定以下三种解决方案:

        1. 您的对象仅引用外部对象。您的对象不应处置此外部对象。这种情况你不需要实现 IDisposable (也就是说,对于这个特定的对象,如果你也在内部构造了一个一次性对象,你就回到上面的第二种情况)。
        2. 您的对象负责外部对象。在这种情况下,您将回到第二种情况,即使您的对象不是构造此外部对象的对象。在这里,您实现 IDisposable,并处置您获得的对象。
        3. 您实施了一种方法,让外界告诉您选择前两种解决方案中的哪一种。例如,可以给构造函数一个连接和一个布尔参数(或理想情况下是一个枚举值),告诉构造函数您的对象现在是否拥有所提供的连接。在这里您还需要实现 IDisposable,但在 Dispose 方法中,您需要检查所有权,并且只有在您拥有时才释放提供的连接。

        那是很多文字,所以让我总结一下:

        1. 您拥有的物品,您需要处理掉。
        2. 您不处理的对象。

        还有第三种情况,听起来不像你有,但尽管如此。

        如果你在本地构造、使用和丢弃一个对象,在单个方法中,而不传递它或将它存储在类的字段中,你可以使用using 语句,如下所示:

        using (IDbConnection conn = ....())
        {
        }
        

        【讨论】:

          【解决方案6】:

          这样做当然是最佳实践,尤其是在处理繁重/非托管对象时。

          编辑:最佳实践,但不是强制性的。

          【讨论】:

            【解决方案7】:

            由于我们永远不知道对象何时会被 GC 回收,因此我们使用 IDisposable 接口有机会在对象被垃圾回收之前有意释放非托管资源。 如果一次性对象在被收集之前没有被释放,那么在退出 AppDomain 之前,它的资源可能不会被释放。 每个引用 IDisposable 对象的对象都应该是 IDisposable 本身,并在其自己的 Dispose 方法中调用其 IDisposable 引用的 Dispose 方法,这几乎是一条不成文的规则。

            【讨论】:

              【解决方案8】:

              当然,如果您使用 C++/CLI,您可以消除 IDisposable 的大量(重新)实现成本,并获得非常接近托管堆上对象的确定性终结的东西。这是一种语言中经常(我发现)被忽视的一个方面,很多人似乎都把它交给了“仅用于胶水代码”的垃圾箱。

              【讨论】:

              • 我不熟悉 C++/CLI,但它听起来是一门有趣的语言。它是否处理了一些 vb.net 和 C# 没有提供合理解决方案的问题案例(例如,当派生类构造函数抛出异常时清理部分构造的对象?)如果是这样,这种处理是否仅限于案例基类和派生类都是用 C++/CLI 编写的,还是在其中一个或另一个(或基类和子派生类)都用其他语言编写的情况下也能提供这种保护?
              【解决方案9】:

              当您使用 Dispose 提供显式控制时,您应该使用 Finalize 方法提供隐式清理。 Finalize 提供了一个备份,以防止在程序员未能调用 Dispose 时资源永久泄漏。

              我认为实现这一点的最佳方法是结合使用 Dispose 和 Finalize 方法。 你可以找到更多Here

              【讨论】:

              • 如果没有调用 Dispose() 方法,Finalizers 会给你一个最后的机会来释放你的 OWN 非托管资源。如果您有非托管资源要清理,则仅实施终结器。终结器不调用成员对象的 Dispose() 方法。相反,您的 dispose 方法应该调用您的终结器(然后取消终结器)。
              • 完全正确,因为不能保证对其他对象的引用在终结器中有效。它们可能以前被收集过。
              猜你喜欢
              • 1970-01-01
              • 2014-01-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-05-30
              • 1970-01-01
              • 2012-09-01
              • 1970-01-01
              相关资源
              最近更新 更多