c# - 正确使用 IDisposable 接口(interface)

标签 c# .net garbage-collection idisposable

我从阅读中知道the Microsoft documentation IDisposable 的“主要”用途接口(interface)是清理非托管资源。

对我来说,“非托管”意味着诸如数据库连接、套接字、窗口句柄等。但是,我已经看到了 Dispose() 的代码。方法用于释放托管资源,这对我来说似乎是多余的,因为垃圾收集器应该为您处理这些。

例如:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

我的问题是,这是否会使 MyCollection 使用的垃圾收集器释放内存?比平时快吗?

编辑 :到目前为止,人们已经发布了一些使用 IDisposable 清理非托管资源(如数据库连接和位图)的好例子。但假设 _theList在上面的代码中包含一百万个字符串,您想立即释放该内存,而不是等待垃圾收集器。上面的代码能做到吗?

最佳答案

Dispose要点释放非托管资源。它需要在某个时候完成,否则它们将永远不会被清理。垃圾收集器不知道 怎么样致电 DeleteHandle()IntPtr 类型的变量上,它不知道是否是否需要拨打DeleteHandle() .

Note: What is an unmanaged resource? If you found it in the Microsoft .NET Framework: it's managed. If you went poking around MSDN yourself, it's unmanaged. Anything you've used P/Invoke calls to get outside of the nice comfy world of everything available to you in the .NET Framework is unmanaged – and you're now responsible for cleaning it up.


您创建的对象需要公开一些外部世界可以调用的方法,以便清理非托管资源。该方法可以随意命名:
public void Cleanup()
要么
public void Shutdown()
但是,此方法有一个标准化名称:
public void Dispose()
甚至创建了一个界面,IDisposable ,只有一种方法:
public interface IDisposable
{
   void Dispose()
}
所以你让你的对象暴露 IDisposable接口(interface),这样你就保证你已经编写了一个方法来清理你的非托管资源:
public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
你已经完成了。 除了你可以做得更好。

如果您的对象分配了 250MB 怎么办? System.Drawing.Bitmap (即 .NET 托管位图类)作为某种帧缓冲区?当然,这是一个托管的 .NET 对象,垃圾收集器将释放它。但是你真的想留下 250MB 的内存只是坐在那里——等待垃圾收集器最终出现并释放它吗?如果有 open database connection 怎么办?当然,我们不希望该连接处于打开状态,等待 GC 完成对象。
如果用户拨打了Dispose() (意味着他们不再计划使用该对象)为什么不摆脱那些浪费的位图和数据库连接?
所以现在我们将:
  • 摆脱非托管资源(因为我们必须这样做),以及
  • 摆脱托管资源(因为我们想提供帮助)

  • 所以让我们更新我们的 Dispose()摆脱这些托管对象的方法:
    public void Dispose()
    {
       //Free unmanaged resources
       Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
    
       //Free managed resources too
       if (this.databaseConnection != null)
       {
          this.databaseConnection.Dispose();
          this.databaseConnection = null;
       }
       if (this.frameBufferImage != null)
       {
          this.frameBufferImage.Dispose();
          this.frameBufferImage = null;
       }
    }
    
    一切都很好,除了你可以做得更好 !

    如果这个人怎么办 忘记了 致电 Dispose()在你的对象上?然后他们会泄漏一些非托管 资源!

    Note: They won't leak managed resources, because eventually the garbage collector is going to run, on a background thread, and free the memory associated with any unused objects. This will include your object, and any managed objects you use (e.g. the Bitmap and the DbConnection).


    如果此人忘记拨打 Dispose() ,我们仍然可以保存他们的培根!我们仍然有办法为他们调用它:当垃圾收集器最终开始释放(即完成)我们的对象时。

    Note: The garbage collector will eventually free all managed objects. When it does, it calls the Finalize method on the object. The GC doesn't know, or care, about your Dispose method. That was just a name we chose for a method we call when we want to get rid of unmanaged stuff.


    垃圾收集器销毁我们的对象是释放那些讨厌的非托管资源的最佳时机。我们通过覆盖 Finalize() 来做到这一点。方法。

    Note: In C#, you don't explicitly override the Finalize() method. You write a method that looks like a C++ destructor, and the compiler takes that to be your implementation of the Finalize() method:

    ~MyObject()
    {
        //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
        Dispose(); //<--Warning: subtle bug! Keep reading!
    }
    
    但是该代码中有一个错误。你看,垃圾收集器在 上运行后台线程 ;您不知道销毁两个对象的顺序。完全有可能在您的 Dispose()代码,托管 您试图摆脱的对象(因为您想提供帮助)不再存在:
    public void Dispose()
    {
       //Free unmanaged resources
       Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
    
       //Free managed resources too
       if (this.databaseConnection != null)
       {
          this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
          this.databaseConnection = null;
       }
       if (this.frameBufferImage != null)
       {
          this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
          this.frameBufferImage = null;
       }
    }
    
    所以你需要的是Finalize()的方法告诉Dispose()应该不碰任何托管 资源(因为它们可能不再存在),同时仍然释放非托管资源。
    执行此操作的标准模式是具有 Finalize()Dispose()双方都致电 第三个 (!) 方法;如果您从 Dispose() 调用它,则传递一个 bool 值说明(与 Finalize() 相反),这意味着释放托管资源是安全的。
    这个内部方法可以被赋予一些任意名称,如“CoreDispose”或“MyInternalDispose”,但传统上称它为Dispose(Boolean) :
    protected void Dispose(Boolean disposing)
    
    但更有用的参数名称可能是:
    protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
    {
       //Free unmanaged resources
       Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
    
       //Free managed resources too, but only if I'm being called from Dispose
       //(If I'm being called from Finalize then the objects might not exist
       //anymore
       if (itIsSafeToAlsoFreeManagedObjects)  
       {    
          if (this.databaseConnection != null)
          {
             this.databaseConnection.Dispose();
             this.databaseConnection = null;
          }
          if (this.frameBufferImage != null)
          {
             this.frameBufferImage.Dispose();
             this.frameBufferImage = null;
          }
       }
    }
    
    然后你改变了 IDisposable.Dispose() 的实现方法:
    public void Dispose()
    {
       Dispose(true); //I am calling you from Dispose, it's safe
    }
    
    和你的终结者:
    ~MyObject()
    {
       Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
    }
    

    Note: If your object descends from an object that implements Dispose, then don't forget to call their base Dispose method when you override Dispose:

    public override void Dispose()
    {
        try
        {
            Dispose(true); //true: safe to free managed resources
        }
        finally
        {
            base.Dispose();
        }
    }
    
    一切都很好,除了你可以做得更好 !

    如果用户拨打Dispose()在您的对象上,那么一切都已清理干净。稍后,当垃圾收集器出现并调用 Finalize 时,它​​将调用 Dispose再次。
    这不仅是浪费,而且如果您的对象对您已经从 处理的对象有垃圾引用。最后 致电 Dispose() ,您将再次尝试处理它们!
    您会注意到,在我的代码中,我小心地删除了对已处理对象的引用,因此我不会尝试调用 Dispose。在垃圾对象引用上。但这并没有阻止一个微妙的错误潜入。
    当用户拨打Dispose()时: handle CursorFileBitmapIconServiceHandle 被摧毁。稍后当垃圾收集器运行时,它会再次尝试销毁同一个句柄。
    protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
    {
       //Free unmanaged resources
       Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
       ...
    }
    
    解决这个问题的方法是告诉垃圾收集器它不需要费心完成对象——它的资源已经被清理干净,不需要更多的工作。您可以通过拨打 GC.SuppressFinalize() 来做到这一点。在 Dispose()方法:
    public void Dispose()
    {
       Dispose(true); //I am calling you from Dispose, it's safe
       GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
    }
    
    现在用户已经拨打了 Dispose() , 我们有:
  • 释放非托管资源
  • 释放托管资源

  • GC 运行终结器毫无意义——一切都已处理完毕。
    我不能使用 Finalize 来清理非托管资源吗?
    Object.Finalize 的文档说:

    The Finalize method is used to perform cleanup operations on unmanaged resources held by the current object before the object is destroyed.


    但是 MSDN 文档也说,对于 IDisposable.Dispose :

    Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.


    那么它是哪个?哪个是我清理非托管资源的地方?答案是:

    It's your choice! But choose Dispose.


    您当然可以将非托管清理放在终结器中:
    ~MyObject()
    {
       //Free unmanaged resources
       Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
    
       //A C# destructor automatically calls the destructor of its base class.
    }
    
    问题是你不知道垃圾收集器什么时候会完成你的对象。您未管理、不需要、未使用的 native 资源将一直存在,直到垃圾收集器最终运行。然后它会调用你的终结器方法;清理非托管资源。 的文档Object.Finalize 指出这一点:

    The exact time when the finalizer executes is undefined. To ensure deterministic release of resources for instances of your class, implement a Close method or provide a IDisposable.Dispose implementation.


    这就是使用Dispose的优点清理非托管资源;您可以了解和控制何时清理非托管资源。它们的破坏是“确定性的”。

    回答您最初的问题:为什么不现在释放内存,而不是在 GC 决定时释放内存?我有一个面部识别软件,需要清除 530 MB 的内部图像 现在 ,因为不再需要它们。如果我们不这样做:机器会停止交换。
    奖励阅读
    对于喜欢这个答案风格的人(解释为什么,所以如何变得明显),我建议你阅读 Don Box 的 Essential COM 的第一章:
  • 直达链接:Chapter 1 sample by Pearson Publishing
  • 磁铁:84bf0b960936d677190a2be355858e80ef7542c0

  • 在 35 页中,他解释了使用二进制对象的问题,并在您眼前发明了 COM。一旦你了解了COM的原因,剩下的300页就很明显了,只是详细说明了微软的实现。
    我认为每个曾经处理过对象或 COM 的程序员至少应该阅读第一章。这是对任何事物的最好解释。
    额外奖励阅读
    When everything you know is wrong archive埃里克·利珀特

    It is therefore very difficult indeed to write a correct finalizer, and the best advice I can give you is to not try.

    关于c# - 正确使用 IDisposable 接口(interface),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/538060/

    相关文章:

    c# - 动态删除菜单项 : Dispose calls Remove

    c# - 高斯平滑公式应用

    c# - 在目录中创建应用程序快捷方式

    c# - Visual Studio 不断崩溃

    .net - 有没有办法/工具来显示系统中所有的内存映射文件?

    Ruby 符号不是垃圾收集的!?那么,使用String不是更好吗?

    ruby - ruby中类实例变量的垃圾回收

    c# - c#中的窗体加载

    .net - 模拟方法返回 null

    经典 ASP 的 IIS6 中的 session 状态和垃圾收集