通过阅读Microsoft文档,我知道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使用的垃圾收集器释放内存比正常情况更快?


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


是的,这段代码完全是多余和不必要的,它不会让垃圾回收器做任何它不会做的事情(即,一旦MyCollection的实例超出范围),尤其是.Clear()调用。

对编辑的回答:有点。如果我这样做:

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

在内存管理方面,它的功能与此相同:

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

如果您真的真的需要立即释放内存,请调用GC.Collect()。内存将在需要时释放。


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.");
        }
    }
}

如果MyCollection无论如何都将被垃圾收集,那么您不需要对其进行处理。这样做只会过度消耗CPU,甚至可能使垃圾收集器已经执行的一些预先计算的分析无效。

我使用IDisposable来执行诸如确保线程以及非托管资源被正确处理之类的操作。

编辑回应斯科特的评论:

GC性能指标唯一受到影响的时间是调用〔sic〕GC.Collect()时“

从概念上讲,GC维护对象引用图的视图,以及线程堆栈帧中对它的所有引用。这个堆可能非常大,并跨越许多页面的内存。作为优化,GC缓存对不太可能经常更改的页面的分析,以避免不必要地重新扫描页面。当页面中的数据发生更改时,GC从内核接收通知,因此它知道页面已脏,需要重新扫描。如果集合在Gen0中,那么页面中的其他内容也可能发生变化,但在Gen1和Gen2中这种情况发生的可能性较小。有趣的是,对于将GC移植到Mac以使Silverlight插件在该平台上工作的团队来说,这些钩子在Mac OS X中不可用。

反对不必要的资源处置的另一点是:想象一个进程正在卸载的情况。想象一下,这个过程已经运行了一段时间。很可能该进程的许多内存页已被交换到磁盘。至少它们不再位于L1或L2缓存中。在这种情况下,正在卸载的应用程序没有必要将所有数据和代码页交换回内存,以“释放”进程终止时操作系统无论如何都会释放的资源。这适用于托管资源,甚至某些非托管资源。只有使非后台线程保持活动状态的资源才能被释放,否则进程将保持活动状态。

现在,在正常执行期间,必须正确清理临时资源(正如@fezmonkey指出的数据库连接、套接字和窗口句柄),以避免非托管内存泄漏。这些都是必须处理的事情。如果您创建了一个拥有线程的类(我的意思是它创建了线程,因此负责确保它停止,至少按照我的编码风格),那么该类很可能必须实现IDisposable,并在Dispose期间拆除线程。

.NET框架使用IDisposable接口作为开发人员必须释放此类的信号,甚至是警告。我想不出框架中实现IDisposable的任何类型(不包括显式接口实现),其中处置是可选的。


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

关于如何实现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),它实际上在两种不同的情况下运行:

dispositing==true:用户代码直接或间接调用了该方法。可以释放托管和非托管资源。dispositing==false:该方法已由运行时从终结器内部调用,不应引用其他对象。只能释放非托管资源。

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

IDisposable和dispose模式的重点不是立即释放内存。对Dispose的调用实际上只有在处理dispositing==false场景和处理非托管资源时才有机会立即释放内存。对于托管代码,在GC运行一个收集循环之前,内存实际上不会被回收,这是您无法控制的(除了调用GC.Collect()之外,我已经提到过这不是一个好主意)。

由于.NET中的字符串不使用任何未更改的资源,也不实现IDisposable,因此您的方案并不真正有效,因此无法强制“清理”它们


在示例代码中,Dispose()操作执行的某些操作可能会产生由于MyCollection对象的正常GC而不会发生的效果。

如果_theList或_theDict引用的对象被其他对象引用,那么List<>或Dictionary<>对象将不会被收集,而是突然没有内容。如果没有像示例中那样的Dispose()操作,那么这些集合仍将包含其内容。

当然,如果是这种情况,我会称之为一个坏的设计——我只是指出(我想是迂腐的)Dispose()操作可能不是完全冗余的,这取决于List<>或Dictionary<>是否有其他未在片段中显示的用途。


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

注意:什么是非托管资源?如果您在Microsoft.NET Framework中找到它:它是托管的。如果你自己去打听MSDN,它是不受管理的。任何您使用P/Invoke调用来脱离.NET Framework中所有可用内容的舒适世界的东西都是非托管的,现在您负责清理它。

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

public void Cleanup()

or

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(),该怎么办?然后他们会泄露一些未管理的资源!

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

如果该人忘记调用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 override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

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


如果用户对您的对象调用Dispose(),那么一切都已清理完毕。稍后,当垃圾收集器出现并调用Finalize时,它将再次调用Dispose。

这不仅是浪费,而且如果您的对象对上次调用Dispose()时已处理的对象有垃圾引用,您将尝试再次处理它们!

您会注意到,在我的代码中,我小心地删除了对已释放对象的引用,所以我不会尝试对垃圾对象引用调用Dispose。但这并没有阻止一个微妙的bug潜入。

当用户调用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的第一章:

直接链接:皮尔逊出版公司第1章样本磁铁:84bf0b960936d677190a2be355858e80ef7542c0

在35页中,他解释了使用二进制对象的问题,并在你眼前发明了COM。一旦你意识到COM的原因,剩下的300页就显而易见了,只需详细介绍一下微软的实现。

我认为每一个处理过对象或COM的程序员至少都应该阅读第一章。这是对任何事情最好的解释。

额外奖励阅读

当你所知道的一切都是错误的

因此,确实很难编写正确的终结器,我能给你的最好建议是不要尝试。


我使用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
}

在对对象调用Dispose后,不应再调用该对象的方法(尽管对象应允许进一步调用Dispose)。因此,问题中的例子是愚蠢的。如果调用Dispose,则可以丢弃对象本身。因此,用户只需丢弃对整个对象的所有引用(将其设置为null),其内部的所有相关对象将自动被清理。

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

归根结底,你可以调用一个函数来让系统进入一种状态,而你可以调用另一个函数来使它恢复到那种状态。现在,在典型示例中,第一个可能是返回文件句柄的函数,第二个可能是对CloseHandle的调用。

但是-这是关键-它们可以是任何匹配的函数对。一个建立一个国家,另一个摧毁它。如果状态已构建但尚未拆除,则资源的实例存在。您必须安排在正确的时间进行拆卸-资源不由CLR管理。唯一自动管理的资源类型是内存。有两种:GC和堆栈。值类型由堆栈管理(或通过在引用类型内搭便车),引用类型由GC管理。

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

看看Justice问题中的例子。对日志文件缩进的更改必须完全嵌套,否则会出错。此外,它们也不太可能是线程安全的。

可以搭上垃圾收集器的便车来清理未管理的资源。但前提是状态更改函数是线程安全的,并且两个状态的生存期可以以任何方式重叠。因此,正义的资源示例不能有终结器!这对任何人都没有帮助。

对于这些类型的资源,您可以只实现IDisposable,而不需要终结器。终结器是绝对可选的-它必须是。这在很多书中都被掩盖了,甚至没有提到。

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

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

还有一种选择,我更喜欢嵌套完美且不线程安全的状态(除了其他之外,避免IDisposable避免了与无法抗拒为每个实现IDisposale的类添加终结器的人发生争论的问题)。

不用编写类,而是编写函数。该函数接受要回调的委托:

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作为一个代码块,因此就像您创建自己的控制结构以实现与使用相同的目的,只是您不再有调用方滥用它的危险。它们不可能不清理资源。

如果资源的生存期可能重叠,那么这种技术就不太有用了,因为这样你就可以构建资源A,然后构建资源B,然后杀死资源A,再杀死资源B。但是,您需要使用IDisposable(但仍然没有终结器,除非您实现了线程安全,这不是免费的)。


如果有的话,我希望代码的效率比不使用它时要低。

调用Clear()方法是不必要的,如果Dispose不这样做,GC可能不会这么做。。。


在您发布的示例中,它仍然没有“立即释放内存”。所有内存都是垃圾收集的,但它可能允许在早期版本中收集内存。你必须运行一些测试才能确定。


框架设计指南是指南,而不是规则。它们告诉你界面主要用于什么,何时使用,如何使用,以及何时不使用。

我曾经读过一段代码,它是一个利用IDisposable失败时的简单RollBack()。下面的MiniTx类将检查Dispose()上的一个标志,如果Commit调用从未发生,它将调用回滚。它添加了一层间接层,使调用代码更易于理解和维护。结果如下:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

我也看到计时/日志代码做了同样的事情。在这种情况下,Dispose()方法停止计时器并记录块已退出。

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

因此,这里有几个具体的示例,它们不执行任何非托管资源清理,但成功地使用IDisposable创建了更干净的代码。


我不再重复关于使用或释放非托管资源的通常内容,这些内容已经全部介绍过了。但我想指出一个常见的误解。给定以下代码

Public Class LargeStuff
  Implements IDisposable
  Private _Large as string()

  'Some strange code that means _Large now contains several million long strings.

  Public Sub Dispose() Implements IDisposable.Dispose
    _Large=Nothing
  End Sub

我意识到一次性实现没有遵循当前的指导原则,但希望你们都能理解。现在,当调用Dispose时,释放了多少内存?答:没有。调用Dispose可以释放非托管资源,它不能回收托管内存,只有GC可以这样做。这并不是说上述模式不是一个好主意,事实上遵循上述模式仍然是一个好想法。一旦Dispose运行完毕,没有什么可以阻止GC重新声明_Large使用的内存,即使LargeStuff的实例可能仍在范围内。_Large中的字符串也可能在第0代,但LargeStuff的实例可能在第2代,因此,内存将很快被重新占用。但是,添加一个finalizer来调用上面显示的Dispose方法是没有意义的。这将延迟内存的重新声明,以允许终结器运行。


IDisposable适用于取消订阅事件。


大多数关于“非托管资源”的讨论的一个问题是,它们并没有真正定义这个术语,但似乎暗示它与非托管代码有关。虽然许多类型的非托管资源确实与非托管代码交互,但用这种术语来思考非托管资源是没有帮助的。

相反,我们应该认识到所有被管理的资源都有共同点:它们都包含一个对象,要求某个外部“事物”代表它做一些事情,而损害了其他一些“事物”,而另一个实体同意这样做,直到另行通知。如果物体被抛弃并消失得无影无踪,那么任何东西都不会告诉外部的“事物”,它不再需要代表不再存在的物体改变自己的行为;因此,“东西”的有用性将永久性地降低。

因此,非托管资源表示外部“事物”代表对象改变其行为的协议,如果对象被放弃并不再存在,这将毫无用处地损害外部“事物的有用性。被管理的资源是一个对象,它是此类协议的受益人,但如果它被放弃,它已签署接收通知,并将使用该通知在其被销毁之前整理其事务。


如果要立即删除,请使用非托管内存。

See:

马歇尔.AllocHGlobalMarshal.FreeHGlobal公司编组.仓库结构


IDisposable/using组合除了主要用作控制系统资源生命周期的方法(完全被Ian的精彩回答所涵盖)外,还可以用于确定(关键)全局资源的状态变化范围:控制台、线程、进程、任何全局对象(如应用程序实例)。

我写了一篇关于这种模式的文章:http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

它说明了如何以可重用和可读的方式保护一些常用的全局状态:控制台颜色、当前线程区域性、Excel应用程序对象财产。。。


首先是定义。对我来说,非托管资源意味着某种类,它实现了IDisposable接口或使用对dll的调用创建的东西。GC不知道如何处理此类对象。例如,如果类只有值类型,那么我不认为这个类是具有非托管资源的类。对于我的代码,我遵循以下实践:

如果由我创建的类使用一些非托管资源,那么这意味着我还应该实现IDisposable接口以清理内存。使用完后立即清理对象。在dispose方法中,我遍历类的所有IDisposable成员并调用dispose。在Dispose方法中,调用GC.SuppressFinalize(this),以通知垃圾收集器我的对象已被清理。我这样做是因为调用GC是一项昂贵的操作。作为额外的预防措施,我尝试多次调用Dispose()。有时我添加私有成员_disposed并签入方法调用,但对象被清理了。如果已清理,则生成ObjectDisposedException以下模板演示了我用文字描述的代码示例:

公共类SomeClass:IDisposable{///<summary>///像往常一样,我不在乎对象是否被处理///</summary>public void SomeMethod(){如果(_disposed)引发新的ObjectDisposedException(“已释放SomeClass实例”);}public void Dispose(){Dispose(真);}私有bool _disposed;受保护的虚拟void Dispose(bool dispositing){如果(_disposed)回来如果(处置)//我们在第一次通话中{}_已处理=真;}}


处置托管资源最合理的用例是为GC回收资源做准备,否则这些资源将永远不会被收集。

一个主要的例子是循环引用。

虽然使用避免循环引用的模式是最佳做法,但如果您最终(例如)得到了一个“子”对象,该对象的引用返回到其“父”,那么如果您只是放弃引用并依赖GC,则这可能会停止父对象的GC收集,而且如果您实现了终结器,则永远不会调用它。

解决此问题的唯一方法是通过将子级上的Parent引用设置为null来手动打破循环引用。

在父母和孩子身上实现IDisposable是实现这一点的最佳方式。当对父级调用Dispose时,对所有子级调用Disposit,并在子级Dispose方法中,将父级引用设置为null。


给定的代码示例不是IDisposable用法的好示例。字典清除通常不应转到Dispose方法。字典项在超出范围时将被清除和处理。需要IDisposable实现来释放一些内存/处理程序,即使它们超出范围也不会释放/释放。

下面的示例显示了IDisposable模式的一个很好的示例,其中包含一些代码和注释。

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

我看到很多答案都转向了讨论对托管和非托管资源使用IDisposable。我认为这篇文章是我找到的关于如何实际使用IDisposable的最佳解释之一。

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

对于实际问题;如果您使用IDisposable清理占用大量内存的托管对象,简短的答案是否定的。原因是,一旦持有内存的对象超出范围,它就可以进行收集了。此时,任何引用的子对象也超出范围,将被收集。

唯一真正的例外是,如果托管对象中占用了大量内存,并且您已经阻止了该线程等待某个操作完成。如果在调用完成后不需要这些对象,那么将这些引用设置为null可能会让垃圾收集器更快地收集它们。但那个场景将代表需要重构的坏代码——而不是IDisposable的用例。