首页 文章

“Dispose”只应用于包含非托管资源的类型吗?

提问于
浏览
64

我最近与一位同事讨论了 Dispose 的 Value 以及实施 IDisposable 的类型 .

我认为实施 IDisposable 对于应该尽快清理的类型是有 Value 的, even if there are no unmanaged resources to clean up .

我的同事有不同的想法;实施 IDisposable 如果你没有必要,因为你的类型最终将被垃圾收集 .

我的论点是,如果你想要尽快关闭ADO.NET连接,那么实现 IDisposableusing new MyThingWithAConnection() 就有意义了 . 我的同事回答说,在封面下,ADO.NET连接是 unmanaged resource . 我对他的答复的答复是 everything ultimately is an unmanaged resource .

我知道recommended disposable pattern你在哪里 free managed and unmanaged resources if Dispose is called 但是 only free unmanaged resources if called via the finalizer/destructor (并且刚刚发表了关于如何alert consumers of improper use of your IDisposable types的博客)

所以,我的问题是,如果你包含非托管资源,是否值得实施 IDisposable

15 回答

  • 3

    IDisposable 有不同的有效用途 . 一个简单的例子就是保存一个打开的文件,只要你不再需要它就需要在某个时刻关闭 . 当然,您可以提供方法 Close ,但在 Dispose 中使用它并使用类似 using (var f = new MyFile(path)) { /*process it*/ } 的模式将更加异常安全 .

    一个更流行的例子是持有一些其他 IDisposable 资源,这通常意味着你需要提供自己的 Dispose 以便处理它们 .

    通常,只要您想要确定性地销毁任何东西,就需要实现 IDisposable .

    我和你的意见之间的区别在于,一旦某些资源需要确定性销毁/释放,我就会尽快实施 IDisposable ,而不是必须尽快实现 . 在这种情况下,依赖垃圾收集不是一种选择(与你同事的说法相反),因为它发生在不可预测的时刻,实际上可能根本不会发生!

    任何资源在封面下都不受管理这一事实并不意味着什么:开发人员应该考虑“何时以及如何处理这个对象”,而不是“它如何在封面下工作” . 无论如何,底层实现可能随时间而变化 .

    实际上,C#和C之间的主要区别之一是缺少默认的确定性破坏 . IDisposable 缩小了差距:您可以订购确定性销毁(虽然您无法确保客户端正在调用它;但在C中您也无法确定客户端在对象上调用 delete ) .


    小补充:确定性释放资源和尽快释放资源之间究竟有什么区别?实际上,那些是不同的(虽然不是完全正交的)概念 .

    如果要确定性地释放资源,这意味着客户端代码应该有可能说"Now, I want this resource freed" . 这可能实际上不是资源可能被释放的最早时刻:持有资源的对象可能已经从资源中获得了所需的一切,因此它可能已经释放资源 . 另一方面,即使在对象的 Dispose 运行之后,对象也可能选择保留(通常是非托管的)资源,仅在终结器中清理它(如果长时间保持资源不会产生任何问题) .

    因此,为了尽快释放资源,严格来说, Dispose 不是必需的:一旦实现自己不再需要资源,对象就可以释放资源 . 然而, Dispose 作为一个有用的暗示,不再需要对象本身,所以如果合适的话,也许可以在那时释放资源 .


    还有一个必要的补充:不仅需要确定性解除分配的非托管资源!这似乎是这个问题答案中意见分歧的关键点之一 . 人们可以拥有纯粹富有想象力的结构,可能需要确定性地释放 .

    例如:访问某些共享结构的权利(想想RW-lock),一个巨大的内存块(想象一下你手动管理一些程序的内存),一个使用其他程序的许可证(想象你不允许运行更多比起某些程序的X副本同时)等 . 这里要释放的对象不是非托管资源,而是做/使用某事的权利,这是纯粹的内在构造你的程序逻辑 .


    小补充:这里有一小部分[ab]使用 IDisposablehttp://www.introtorx.com/Content/v1.0.10621.0/03_LifetimeManagement.html#IDisposable的简洁例子 .

  • 6

    我认为在责任方面考虑 IDisposable 是最有帮助的 . 一个对象应该实现 IDisposable ,如果它知道在它之间需要完成的事情,那么它就是唯一具有信息和推动力的对象 . 例如,打开文件的对象将有责任查看该文件是否已关闭 . 如果对象只是在不关闭文件的情况下消失,则文件可能无法在任何合理的时间范围内关闭 .

    重要的是要注意,即使只与100%托管对象交互的对象也可以执行需要清理的事情(并且应该使用 IDisposable ) . 例如,附加到集合的"modified"事件的 IEnumerator 将需要在不再需要时自行分离 . 否则,除非枚举器使用一些复杂的技巧,否则只要集合在范围内,枚举器就永远不会被垃圾收集 . 如果集合被枚举了一百万次,那么一百万个枚举器将附加到它的事件处理程序 .

    请注意,有时可以使用终结器进行清理,无论出于何种原因,在没有首先调用 Dispose 的情况下放弃对象 . 有时候效果很好;有些东西很糟糕 . 例如,即使 Microsoft.VisualBasic.Collection 使用终结器从"modified"事件中分离枚举器,尝试枚举这样的对象数千次而没有干预 Dispose 或垃圾收集将导致它变得非常慢 - 比性能慢许多个数量级如果正确使用 Dispose 会导致

  • 3

    根本没有必要的资源(托管或非托管) . 通常, IDisposable 只是一种方便的方法来消除精炼 try {..} finally {..} ,只需比较:

    Cursor savedCursor = Cursor.Current;
    
      try {
        Cursor.Current = Cursors.WaitCursor;
    
        SomeLongOperation();
      }
      finally {
        Cursor.Current = savedCursor;
      }
    

    using (new WaitCursor()) {
        SomeLongOperation();
      }
    

    其中 WaitCursorIDisposable 适合 using

    public sealed class WaitCursor: IDisposable {
        private Cursor m_Saved;
    
        public Boolean Disposed {
          get;
          private set;
        }
    
        public WaitCursor() {
          Cursor m_Saved = Cursor.Current;
          Cursor.Current = Cursors.WaitCursor;
        }
    
        public void Dispose() {
          if (!Disposed) {
            Disposed = true;
            Cursor.Current = m_Saved;
          }
        }
      }
    

    您可以轻松地组合这些类:

    using (new WaitCursor()) {
        using (new RegisterServerLongOperation("My Long DB Operation")) {
          SomeLongRdbmsOperation();  
        }
    
        SomeLongOperation();
      }
    
  • 1

    所以,我的问题是,如果你有一个不包含非托管资源的类型,是否值得实现IDisposable?

    当有人在一个对象上放置一个IDisposable接口时,这告诉我创建者打算在这个方法中做某事,或者在将来他们可能打算这样做 . 我总是在这个例子中称为dispose只是为了确定 . 即使它现在没有做任何事情,它可能在未来,并且因为更新了对象而导致内存泄漏很糟糕,并且在第一次编写代码时没有调用Dispose .

    事实上,这是一个判断 . 你不想过度实现它,因为在那一点上为什么还要烦扰垃圾收集器 . 为什么不手动处理每个对象 . 如果您有可能需要处置非托管资源,那么这可能不是一个坏主意 . 这完全取决于,如果使用您的对象的唯一人员是您团队中的人员,您可以随后跟进他们并说:“嘿,现在需要使用非托管资源 . 我们必须仔细检查代码并确保我们整理了一下 . “如果您要将其发布给其他组织以使用其他组织 . 没有简单的方法可以告诉每个可能已经实现该对象的人,“嘿,你需要确保现在已经处理掉了 . ”让我告诉你,有些事情让人们感到羞耻,而不是升级第三方程序集,以发现他们是那些更改代码并使你的应用程序逃避内存问题的人 .

    我的同事回答说,在幕后,ADO.NET连接是一个托管资源 . 我对他回复的答复是,最终所有东西都是一种非托管资源 .

    他是对的,现在是一个托管资源 . 他们会改变吗?谁知道,但称之为没有伤害 . 我没有尝试猜测ADO.NET团队做了什么,所以如果他们把它放入并且什么都不做,那就没问题 . 我仍然会称之为,因为一行代码不会影响我的工作效率 .

    您还遇到了另一种情况 . 假设您从方法返回ADO.NET连接 . 您不知道ADO连接是基础对象还是派生类型 . 您不知道是否突然需要该IDisposable实现 . 无论如何,我总是称之为,因为当 生产环境 服务器每4小时崩溃时,追踪 生产环境 服务器上的内存泄漏就会很糟糕 .

  • 1

    虽然已经有了很好的答案,但我只是想明确一些 .

    实施 IDisposable 有三种情况:

    • 您正在直接使用非托管资源 . 这通常涉及从P / Invoke调用中检索 IntPrt 或其他形式的句柄必须通过不同的P / Invoke调用发布

    • 您正在使用其他 IDisposable 对象,需要对其处置负责

    • 您还有其他需要或使用它,包括 using 块的便利性 .

    虽然我可能有点偏颇,但你应该阅读(并向你的同事展示)the StackOverflow Wiki on IDisposable .

  • 1

    Dispose 应该用于具有有限生命期的任何资源 . 终结器应该用于任何非托管资源 . 任何非托管资源都应该具有有限的生命周期,但是有大量的托管资源(如锁)也具有有限的生命周期 .

  • 9

    请注意,非托管资源可能包含标准CLR对象,例如保存在某些静态字段中,所有对象都以安全模式运行,根本没有非托管导入 .

    没有简单的方法可以判断实现 IDiposable 的给定类是否确实需要清理某些东西 . 我的经验法则是总是在我不太了解的对象上调用 Dispose ,就像某些第三方库一样 .

  • 3

    不,它只是非托管资源的 not .

    它被建议像框架调用的基本清理内置机制,使您可以清理所需的任何资源,但最适合的是自然的非托管资源管理 .

  • 0

    如果你聚合 IDisposable 然后你应该实现接口,以便及时清理这些成员 . 如何在你引用的ADO.Net连接示例中调用 myConn.Dispose()

    在这种情况下,我不能正确地说一切都是非托管资源 . 我也不同意你的同事 .

  • 35

    你是对的 . 托管数据库连接,文件,注册表项,套接字等都保留在非托管对象上 . 这就是他们实施 IDisposable 的原因 . 如果您的类型拥有一次性对象,则应实现 IDisposable 并将其置于 Dispose 方法中 . 否则,他们可能会保持活着,直到收集垃圾导致锁定文件和其他意外行为 .

  • 5

    一切最终都是一种非托管资源 .

    不对 . CLR对象使用的内存以外的所有内容,只由框架管理(分配和释放) .

    实现 IDisposable 并调用 Dispose on an object that does not hold on to any unmanaged resources (直接或间接通过依赖对象)是 pointless . 它确实释放了该对象 deterministic ,因为它始终只有 GC 这样做 . 对象是引用类型,因为值类型在直接在方法级别使用时,由堆栈操作分配/释放 .

    现在,每个人都声称他们的答案是正确的 . 让我 prove 我的 . 根据documentation

    Object.Finalize Method允许对象在垃圾收集回收之前尝试释放资源并执行其他清理操作 .

    换句话说,在调用 Object.Finalize() 之后释放对象的CLR内存 . [注意:如果需要,可以明确跳过此调用]

    这是一个没有非托管资源的一次性类:

    internal class Class1 : IDisposable
    {
        public Class1()
        {
            Console.WriteLine("Construct");
        }
    
        public void Dispose()
        {
            Console.WriteLine("Dispose");
        }
    
        ~Class1()
        {
            Console.WriteLine("Destruct");
        }
    }
    

    请注意destructor隐式调用继承链中的每个 FinalizeObject.Finalize()

    这是控制台应用程序的 Main 方法:

    static void Main(string[] args)
    {
        for (int i = 0; i < 10; i++)
        {
            Class1 obj = new Class1();
            obj.Dispose();
        }
    
        Console.ReadKey();
    }
    

    如果调用 Dispose 是一种以确定的方式释放 managed 对象的方法,那么每个"Dispose"会紧跟一个"Destruct",对吧?看看自己会发生什么 . 从命令行窗口运行此应用程序是最有趣的 .

    注意:有一种方法可以强制 GC 收集当前应用程序域中待定位的所有对象,但对于单个特定对象则不会 . 不过,您无需调用 Dispose 即可在终结队列中拥有对象 . 强烈建议不要强行收集,否则可能会影响整体应用程序的性能 .

    EDIT

    有一个例外 - 国家管理 . 如果您的对象恰好管理外部状态, Dispose 可以处理状态更改 . 即使状态不是非托管对象,由于特殊处理_1147474_,因此使用它非常方便 . 示例将是安全上下文或模拟上下文 .

    using (WindowsImpersonationContext context = SomeUserIdentity.Impersonate()))
    {
        // do something as SomeUser
    }
    
    // back to your user
    

    这不是最好的例子,因为 WindowsImpersonationContext 在内部使用了系统句柄,但是你得到了图片 .

    底线是,在实施 IDisposable 时,您需要(或计划拥有) Dispose 方法中有意义的事情 . 否则,这只是浪费时间 . IDisposable 确实不会更改GC管理对象的方式 .

  • 17

    如果类型引用非托管资源,或者它包含对实现IDisposable的对象的引用,则应该实现IDisposable .

  • 0

    在我的一个项目中,我有一个带有托管线程的类,我们将它们称为线程A,线程B和IDisposable对象,我们称之为C.

    用于处理退出时的C. B曾经使用C来保存异常 .

    我的 class 必须实施IDisposable和descrtuctor,以确保按正确的顺序处理事情 . 是的,GC可以清理我的物品,但我的经验是有一个竞争条件,除非我管理我的 class 清理 .

  • 5

    简答:绝对不是 . 如果您的类型具有托管或非托管成员,则应实现IDisposable .

    现在详细说明:我已回答了这个问题,并在StackOverflow上提供了有关内存管理内容和GC问题的更多细节 . 这里仅仅是少数:

    关于IDisposable实施的最佳实践,请参阅我的博客文章:

    How do you properly implement the IDisposable pattern?

  • 5

    Implement IDisposable if the object owns any unmanaged objects or any managed disposable objects

    如果对象使用非托管资源,则应实现 IDisposable . 拥有一次性对象的对象应实现 IDisposable 以确保释放基础非托管资源 . 如果遵循规则/约定,则可以合理地得出结论:不处理托管的一次性对象等于不释放非托管资源 .

相关问题