dispose用法 - 可恶的c# idisposable接口




正确使用IDisposable接口 (12)

Dispose模式的目的是提供一种机制来清理托管资源和非托管资源,并在何时发生取决于如何调用Dispose方法。 在您的示例中,Dispose的使用实际上并未执行与处置有关的任何操作,因为清除列表对处理该集合没有影响。 同样,将变量设置为null的调用也不会影响GC。

你可以看看这篇article ,了解如何实现Dispose模式的更多细节,但它基本如下所示:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

这里最重要的方法是Dispose(bool),它实际上在两种不同的情况下运行:

  • 处置== true:该方法直接或间接由用户代码调用。 可以处置托管和非托管资源。
  • 处置== false:该方法已由运行时从终结器中调用,并且不应引用其他对象。 只能处理非托管资源。

简单地让GC负责清理的问题是,您无法真正控制GC何时运行一个收集周期(您可以调用GC.Collect(),但实际上不应该这样做),因此资源可能会停留比需要的时间长。 请记住,调用Dispose()实际上并不会导致收集周期或以任何方式导致GC收集/释放对象; 它只是提供了更确定性地清理所使用的资源的方法,并告诉GC该清理已经执行。

IDisposable的全部和配置模式并不是立即释放内存。 唯一一次对Dispose的调用实际上甚至有可能立即释放内存的时间是处理处置== false场景和处理非托管资源的时间。 对于托管代码,直到GC运行一个你真正无法控制的收集周期(除了调用GC.Collect(),我已经提到这不是一个好主意)之前,内存实际上不会被回收。

你的场景并不真正有效,因为.NET中的字符串没有使用任何未经处理的资源,也没有实现IDisposable,所以没有办法强制它们被“清理”。

通过阅读MSDN文档 ,我知道IDisposable接口的“主要”用途是清理非托管资源。

对我而言,“非托管”意味着像数据库连接,套接字,窗口句柄等。但是,我已经看到代码实现了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使用的垃圾回收器可用内存比MyCollection更快?

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


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

注意 :什么是非托管资源 ? 如果您在Microsoft .NET Framework中找到它:它是托管的。 如果你自己去了解MSDN,那么它是非托管的。 任何你使用过P / Invoke调用的东西都不在.NET框架中可用的所有可用的舒适的世界之外,而且你现在负责清理它。

你创建的对象需要公开一些方法,外部世界可以调用,以清理非托管资源。 该方法可以任意命名:

public void Cleanup()

public void Shutdown()

相反,这种方法有一个标准化的名称:

public void Dispose()

甚至有一个创建的接口, IDisposable ,只有一个方法:

public interface IDisposable
{
   void Dispose()
}

所以你让你的对象公开了IDisposable接口,这样你就保证你已经编写了这个单一的方法来清理你的非托管资源:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

你完成了。 除非你能做得更好。

如果你的对象已经分配了一个250MB的System.Drawing.Bitmap (即.NET管理的Bitmap类)作为某种帧缓冲区? 当然,这是一个托管的.NET对象,垃圾收集器将释放它。 但是,你真的想离开250MB的内存吗 - 等待垃圾收集器最终来到并释放它? 如果有一个开放的数据库连接会怎么样? 当然,我们不希望这个连接处于打开状态,等待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()会怎么样? 然后他们会泄露一些非托管资源!

注意:它们不会泄漏托管资源,因为垃圾收集器最终将在后台线程上运行,并释放与任何未使用的对象关联的内存。 这将包括您的对象和您使用的任何管理对象(例如BitmapDbConnection )。

如果这个人忘了叫Dispose() ,我们仍然可以保存他们的培根! 我们仍然有一种方法可以他们调用它:当垃圾回收器最终获得释放(即最终化)我们的对象时。

注意:垃圾收集器最终将释放所有管理对象。 当它发生时,它会调用对象上的Finalize方法。 GC不知道或关心Dispose方法。 这只是我们选择的一个名称,当我们想要摆脱不受管理的东西时,我们称之为方法。

垃圾收集器破坏我们的对象是释放这些烦人的非托管资源的最佳时机。 我们通过重写Finalize()方法来做到这一点。

注意:在C#中,您没有显式重写Finalize()方法。 您编写一个看起来像 C ++析构函数的方法,编译器将其作为Finalize()方法的实现:

~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() (而不是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
}

注意 :如果您的对象从实现Dispose对象下降,那么当您重写Dispose时,请不要忘记调用它们的基本 Dispose方法:

public Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

一切都很好, 除非你能做得更好

如果用户在对象上调用Dispose() ,则所有内容都已清理完毕。 稍后,当垃圾收集器出现并调用Finalize时,它将再次调用Dispose

这不仅浪费,而且如果你的对象有垃圾引用,这些对象是你从上次调用Dispose()已经处理过的对象,那么你将尝试再次处理它们!

你会注意到在我的代码中我很小心地移除了我已经Dispose的对象的引用,所以我不试图在垃圾对象引用上调用Dispose 。 但是这并没有阻止一个微妙的错误蔓延。

当用户调用Dispose() ,句柄CursorFileBitmapIconServiceHandle被销毁。 稍后当垃圾收集器运行时,它将尝试再次销毁相同的句柄。

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

你解决这个问题的方式是告诉垃圾收集器它不需要费心去完成对象 - 它的资源已经被清理了,不需要更多的工作。 您可以通过在Dispose()方法中调用GC.SuppressFinalize()来完成此操作:

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的文档说:

Finalize方法用于在对象销毁之前对当前对象持有的非托管资源执行清理操作。

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

执行与释放,释放或重置非托管资源相关的应用程序定义的任务。

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

这是你的选择! 但选择Dispose

您当然可以将您的非托管清理放入终结器中:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

这个问题是你不知道什么时候垃圾收集器会绕过来完成你的对象。 您的未管理的,不需要的,未使用的本地资源将一直存在,直到垃圾收集器最终运行。 然后它会调用你的终结器方法; 清理非托管资源。 Object.Finalize的文档指出:

终结器执行的确切时间是未定义的。 为确保为您的类的实例确定性地释放资源,请实现Close方法或提供一个IDisposable.Dispose实现。

这是使用Dispose清理非托管资源的优点; 你会知道,并控制,当非托管资源被清理。 他们的破坏是“确定性的”

回答你原来的问题:为什么不现在释放记忆,而不是当GC决定这么做? 我有一个面部识别软件,现在需要摆脱530 MB的内部图像,因为它们不再需要。 当我们不这样做时:机器会停下来交换。

奖金阅读

对于喜欢这个答案的风格的人(解释为什么 ,所以如何变得明显),我建议你阅读Don Box的基本COM的第一章:

在35页中,他解释了使用二进制对象的问题,并在您的眼前发明COM。 一旦你意识到COM的原因 ,其余的300页是显而易见的,只是详细描述了微软的实施。

我认为每个曾经处理过对象或COM的程序员都应该至少阅读第一章。 这是有史以来最好的解释。

额外的奖金阅读

当你知道的所有事情都是由Eric Lippert 错误

因此,编写一份正确的终结器确实非常困难, 我可以给你的最好建议是不要尝试


Apart from its primary use as a way to control the lifetime of system resources (completely covered by the awesome answer of Ian , kudos!), the IDisposable/using combo can also be used to scope the state change of (critical) global resources : the console , the threads , the process , any global object like an application instance .

I've written an article about this pattern: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

It illustrates how you can protect some often used global state in a reusable and readable manner: console colors , current thread culture , Excel application object properties ...


First of definition. For me unmanaged resource means some class, which implements IDisposable interface or something created with usage of calls to dll. GC doesn't know how to deal with such objects. If class has for example only value types, then I don't consider this class as class with unmanaged resources. For my code I follow next practices:

  1. If created by me class uses some unmanaged resources then it means that I should also implement IDisposable interface in order to clean memory.
  2. Clean objects as soon as I finished usage of it.
  3. In my dispose method I iterate over all IDisposable members of class and call Dispose.
  4. In my Dispose method call GC.SuppressFinalize(this) in order to notify garbage collector that my object was already cleaned up. I do it because calling of GC is expensive operation.
  5. As additional precaution I try to make possible calling of Dispose() multiple times.
  6. Sometime I add private member _disposed and check in method calls did object was cleaned up. And if it was cleaned up then generate ObjectDisposedException
    Following template demonstrates what I described in words as sample of code:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }

If anything, I'd expect the code to be less efficient than when leaving it out.

Calling the Clear() methods are unnecessary, and the GC probably wouldn't do that if the Dispose didn't do it...



One problem with most discussions of "unmanaged resources" is that they don't really define the term, but seem to imply that it has something to do with unmanaged code. While it is true that many types of unmanaged resources do interface with unmanaged code, thinking of unmanaged resources in such terms isn't helpful.

Instead, one should recognize what all managed resources have in common: they all entail an object asking some outside 'thing' to do something on its behalf, to the detriment of some other 'things', and the other entity agreeing to do so until further notice. If the object were to be abandoned and vanish without a trace, nothing would ever tell that outside 'thing' that it no longer needed to alter its behavior on behalf of the object that no longer existed; consequently, the 'thing's usefulness would be permanently diminished.

An unmanaged resource, then, represents an agreement by some outside 'thing' to alter its behavior on behalf of an object, which would useless impair the usefulness of that outside 'thing' if the object were abandoned and ceased to exist. A managed resource is an object which is the beneficiary of such an agreement, but which has signed up to receive notification if it is abandoned, and which will use such notification to put its affairs in order before it is destroyed.


The most justifiable use case for disposal of managed resources, is preparation for the GC to reclaim resources that would otherwise never be collected.

A prime example is circular references.

Whilst it's best practice to use patterns that avoid circular references, if you do end up with (for example) a 'child' object that has a reference back to its 'parent', this can stop GC collection of the parent if you just abandon the reference and rely on GC - plus if you have implemented a finalizer, it'll never be called.

The only way round this is to manually break the circular references by setting the Parent references to null on the children.

Implementing IDisposable on parent and children is the best way to do this. When Dispose is called on the Parent, call Dispose on all Children, and in the child Dispose method, set the Parent references to null.


Yep, that code is completely redundant and unnecessary and it doesn't make the garbage collector do anything it wouldn't otherwise do (once an instance of MyCollection goes out of scope, that is.) Especially the .Clear() calls.

Answer to your edit: Sort of. If I do this:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

It's functionally identical to this for purposes of memory management:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

If you really really really need to free the memory this very instant, call GC.Collect() . There's no reason to do this here, though. The memory will be freed when it's needed.


在对其调用Dispose之后,不应再有对对象的方法的调用(尽管对象应该允许进一步调用Dispose)。 因此这个问题中的例子很愚蠢。 如果调用Dispose,那么该对象本身可以被丢弃。 所以用户应该放弃对整个对象的所有引用(将它们设置为空),并且内部的所有相关对象都会自动清理。

至于关于托管/非托管以及其他答案中的讨论的一般性问题,我认为对这个问题的任何回答都必须从非托管资源的定义开始。

归结起来,有一个函数可以调用来使系统进入状态,还有另一个函数可以调用以使其退出状态。 现在,在典型的例子中,第一个可能是一个返回文件句柄的函数,第二个可能是对CloseHandle的调用。

但是 - 这是关键 - 它们可以是任何匹配的功能对。 一个建立一个国家,另一个将其撕下。 如果该州已建成但尚未拆除,则存在该资源的实例。 您必须安排在合适的时间进行拆卸 - 资源不由CLR管理。 唯一自动管理的资源类型是内存。 有两种:GC和堆栈。 值类型由堆栈管理(或通过在引用类型内部搭建),引用类型由GC管理。

这些函数可能会导致可以自由交错的状态更改,或者可能需要完全嵌套。 状态更改可能是线程安全的,也可能不是。

看看正义问题中的例子。 对日志文件的缩进进行的更改必须完美嵌套,否则一切都会出错。 他们也不太可能是线程安全的。

有可能搭上垃圾收集器来清理你的非托管资源。 但是,只有当状态改变函数是线程安全的并且两个状态可以有任何重叠的生命周期时。 所以正义的资源的例子不能有一个终结者! 它不会帮助任何人。

对于这些资源,只需实现IDisposable ,无需终结器。 终结者是绝对可选的 - 它必须是。 这在许多书中都被掩盖了,甚至没有提及。

然后您必须使用using语句来确保调用Dispose 。 这基本上就像搭上堆栈(因为终结器是GC, using堆栈)。

缺少的部分是您必须手动编写Dispose并将其调用到您的字段和基类中。 C ++ / CLI程序员不必这样做。 编译器在大多数情况下为它们编写它。

还有一个替代方案,我更喜欢那些嵌套完美且不是线程安全的状态(除了别的之外,避免使用IDisposable来替代无法为每个实现IDisposable的类添加终结器的人的争论问​​题) 。

写一个函数,而不是写一个类。 该函数接受委托来回叫:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

然后一个简单的例子是:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

被传入的lambda用作代码块,因此它就像您自己的控制结构与using相同的目的一样,除了您不再有主叫方滥用它的危险之外。 他们无法清理资源。

如果资源是可能具有重叠生命期的那种,那么这种技术就不那么有用了,因为那时你想要能够建立资源A,然后资源B,然后杀死资源A,然后杀死资源B.你不能那样做如果你迫使用户完全像这样嵌套。 但是那么你需要使用IDisposable (但是仍然没有终结器,除非你已经实现了threadsafety,它不是免费的)。


我使用IDisposable方案:清理非托管资源,取消订阅事件,关闭连接

我用于实现IDisposable( 不是线程安全 )的习惯用法:

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}

通常使用IDisposable来利用using语句,并利用一种简单的方法对托管对象进行确定性清理。

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}




idisposable