1801

我通过阅读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想释放该内存,而不是等待垃圾收集器。上面的代码能做到这一点吗?

4

19 回答 19

2795

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

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

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

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()你的对象怎么办?然后他们会泄漏一些非托管资源!

注意:它们不会泄漏托管资源,因为最终垃圾收集器将在后台线程上运行,并释放与任何未使用对象关联的内存。这将包括您的对象以及您使用的任何托管对象(例如 theBitmap和 the 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垃圾对象引用。但这并没有阻止一个微妙的错误潜入。

当用户调用时Dispose():句柄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说:

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 的 Essential COM 的第一章:

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

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

额外的奖金阅读

当你知道的一切都是错误的 存档时埃里克·利珀特

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

于 2009-02-11T18:55:20.617 回答
76

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.");
        }
    }
}
于 2009-02-11T18:20:41.170 回答
46

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

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

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

IDisposable 和 dispose 模式的重点不是立即释放内存。对 Dispose 的调用实际上甚至有机会立即释放内存的唯一一次是在它处理 disposing == false 场景和操作非托管资源时。对于托管代码,内存实际上不会被回收,直到 GC 运行一个收集周期,你真的无法控制(除了调用 GC.Collect(),我已经提到这不是一个好主意)。

您的方案实际上并不有效,因为 .NET 中的字符串不使用任何未管理的资源并且不实现 IDisposable,因此无法强制“清理”它们。

于 2009-02-11T18:42:51.490 回答
22

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

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

归结为,您可以调用一个函数将系统置于某种状态,并且可以调用另一个函数将其从该状态中恢复。现在,在典型示例中,第一个可能是返回文件句柄的函数,第二个可能是对CloseHandle.

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

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

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

可以搭便车与垃圾收集器一起清理非托管资源。但前提是状态更改函数是线程安全的,并且两个状态的生命周期可以以任何方式重叠。因此,Justice 的资源示例不能有终结器!它只是不会帮助任何人。

对于这些类型的资源,您可以只实现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(但仍然没有终结器,除非你已经实现了线程安全,它不是免费的)。

于 2009-02-11T20:21:05.683 回答
17

我使用 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
}
于 2009-02-11T19:31:38.020 回答
13

是的,该代码是完全多余和不必要的,它不会使垃圾收集器做任何它不会做的事情(一旦 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(). 不过,这里没有理由这样做。内存将在需要时被释放。

于 2009-02-11T18:19:27.303 回答
12

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

IDisposable过去常常做一些事情,例如确保正确处理线程以及非托管资源。

编辑回应斯科特的评论:

影响 GC 性能指标的唯一时间是调用 [sic] GC.Collect() 时”

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

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

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

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

于 2009-02-11T18:20:56.223 回答
7

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


框架设计指南是指南,而不是规则。它们会告诉您界面的主要用途、何时使用、如何使用以及何时不使用。

我曾经读过代码,它是一个简单的 RollBack() 使用 IDisposable 失败时。下面的 MiniTx 类将检查 Dispose() 上的标志,如果Commit调用从未发生过,它将调用Rollback自身。它增加了一层间接性,使调用代码更容易理解和维护。结果看起来像:

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 创建更清晰的代码。

于 2009-02-11T21:07:01.827 回答
7

我不会重复有关使用或释放​​非托管资源的常见内容,这些内容都已涵盖。但我想指出一个常见的误解。
给定以下代码

公共课LargeStuff
  实现 IDisposable
  Private _Large as string()

  '一些奇怪的代码意味着 _Large 现在包含数百万长字符串。

  Public Sub Dispose() 实现 IDisposable.Dispose
    _大=没有
  结束子

我意识到 Disposable 实现不遵循当前的指导方针,但希望你们都明白这一点。
现在,当调用 Dispose 时,释放了多少内存?

答:没有。
调用 Dispose 可以释放非托管资源,它不能回收托管内存,只有 GC 可以这样做。这并不是说上面不是一个好主意,实际上遵循上面的模式仍然是一个好主意。运行 Dispose 后,没有什么可以阻止 GC 重新声明 _Large 正在使用的内存,即使 LargeStuff 的实例可能仍在范围内。_Large 中的字符串也可能在 gen 0 中,但 LargeStuff 的实例可能在 gen 2 中,所以同样,内存会被更快地回收。
但是,添加终结器来调用上面显示的 Dispose 方法是没有意义的。这只会延迟内存的重新声明以允许终结器运行。

于 2009-02-11T21:08:56.380 回答
7

如果您想立即删除,请使用非托管内存

看:

于 2013-06-03T21:07:50.083 回答
5

如果有的话,我希望代码的效率低于将其排除在外时的效率。

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

于 2009-02-11T20:32:25.670 回答
5

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

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

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

于 2013-06-15T13:56:16.593 回答
3

您给定的代码示例不是一个很好的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.
    }
}
于 2017-05-26T05:41:42.740 回答
3

我看到很多答案已经转向谈论将 IDisposable 用于托管和非托管资源。我建议这篇文章作为我找到的关于 IDisposable 应该如何实际使用的最佳解释之一。

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

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

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

于 2018-10-03T17:32:35.177 回答
2

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

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

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

于 2009-02-11T18:45:58.753 回答
2

IDisposable有利于退订事件。

于 2012-02-20T16:35:08.233 回答
2

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

相反,人们应该认识到所有托管资源的共同点:它们都需要一个对象要求一些外部“事物”代表它做某事,而损害一些其他“事物”,并且另一个实体同意这样做,直到另行通知。如果对象被遗弃并消失得无影无踪,那么没有任何东西可以告诉外部“事物”它不再需要代表不再存在的对象改变其行为;因此,这件东西的用处将永久减少。

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

于 2012-02-22T06:38:32.443 回答
2

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

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

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;
        }
    }
于 2015-08-31T09:45:02.890 回答
1

处置托管资源的最合理用例是准备 GC 回收否则永远不会收集的资源。

一个典型的例子是循环引用。

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

解决此问题的唯一方法是通过将子项上的父引用设置为 null 来手动中断循环引用。

在父母和孩子上实现 IDisposable 是最好的方法。在 Parent 上调用 Dispose 时,对所有 Child 调用 Dispose,并在子 Dispose 方法中,将 Parent 引用设置为 null。

于 2016-09-14T05:40:49.443 回答