题 正确使用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 比通常更快?

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


1382
2018-02-11 18:12


起源


我喜欢接受的答案,因为它告诉你使用IDisposable的正确“模式”,但就像OP在他的编辑中说的那样,它没有回答他想要的问题。 IDisposable不会“调用”GC,它只是将某个对象“标记”为可销毁的。但是,“现在”免费记忆的真正方法是什么,而不是等待GC开始?我认为这个问题值得更多讨论。 - Punit Vora
IDisposable 没有标记任何东西。该 Dispose 方法执行必须执行的操作来清理实例使用的资源。这与GC无关。 - John Saunders
@约翰。我明白了 IDisposable。这就是为什么我说接受的答案没有回答OP关于IDisposable是否有助于<i>释放内存</ i>的预期问题(和后续编辑)。以来 IDisposable 与释放内存无关,只有资源,然后就像你说的那样,根本没有必要将托管引用设置为null,这正是OP在他的例子中所做的。因此,对他的问题的正确答案是“不,它无助于更快地释放内存。事实上,它根本没有帮助释放内存,只有资源”。但无论如何,感谢您的投入。 - Punit Vora
@desigeek:如果是这种情况,那么你不应该说“IDisposable不会'调用'GC,只是'标记'一个对象可以破坏” - John Saunders
@desigeek:没有保证确定性地释放内存的方法。您可以调用GC.Collect(),但这是礼貌请求,而不是需求。必须暂停所有正在运行的线程以进行垃圾收集 - 如果您想了解更多信息,请阅读.NET安全点的概念,例如: msdn.microsoft.com/en-us/library/678ysw69(v=vs.110).aspx 。如果线程无法暂停,例如因为有一个非托管代码的调用,GC.Collect()可能什么都不做。 - Concrete Gannet


答案:


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

注意:什么是 非托管资源?如果您在Microsoft .NET Framework中找到它:它是受管理的。如果你自己去探索MSDN,它是不受管理的。你曾经使用P / Invoke调用的任何东西都可以在.NET Framwork中为你提供所有可用的舒适世界,而且你现在负责清理它。

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

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

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

如果该人忘了打电话 Dispose(), 我们可以 仍然 保存他们的培根!我们还有办法打电话给它 对于 它们:当垃圾收集器最终绕过以释放(即完成)我们的对象时。

注意: 垃圾收集器最终将释放所有托管对象。   当它发生时,它会调用 Finalize   关于对象的方法。 GC不知道,或   关心 你的  部署 方法。   那只是我们选择的名字   我们想要得到的方法   摆脱不受管理的东西。

垃圾收集器破坏了我们的对象 完善 是时候释放那些讨厌的非托管资源了。我们这样做是为了超越 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(): 手柄 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 指出这一点:

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

这是使用的优点 Dispose 清理非托管资源;当清理非托管资源时,您将了解并控制。他们的毁灭是 “确定性”


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

奖金阅读

对于任何喜欢这种答案风格的人(解释一下) 为什么, 所以 怎么样 变得明显了),我建议你阅读Don Box的Essential COM第一章:

在35页中,他解释了使用二进制对象的问题,并在您眼前发明了COM。一旦你意识到了 为什么 COM的剩余300页是显而易见的,只是详细介绍了Microsoft的实现。

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

额外奖金阅读

当你知道的一切都是错的 作者:Eric Lippert

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


2287
2018-02-11 18:20



你可以做得更好 - 你需要在Dispose中添加对GC.SuppressFinalize()的调用。 - plinth
@Daniel Earwicker:这是真的。微软希望你完全停止使用Win32,并坚持使用可抽象的,可移植的,独立于设备的.NET Framework调用。如果你想在下面的操作系统周围戳;因为你 认为 你知道什么操作系统正在运行:你将掌握自己的生命。并非每个.NET应用程序都在Windows或桌面上运行。 - Ian Boyd
这是一个很好的答案,但我认为它会受益于标准案例的最终代码列表以及类派生自已实现Dispose的基类的情况。例如在这里阅读(msdn.microsoft.com/en-us/library/aa720161%28v=vs.71%29.aspx当我从已经实现Dispose的类派生时,我也会对我应该做什么感到困惑(嘿,我是新手)。 - integra753
@GregS和其他人:通常我不打扰设置引用 null。首先,它意味着你无法制造它们 readonly,其次,你必须做得非常难看 !=null 检查(如示例代码中所示)。你可以有一面旗帜 disposed,但更容易不打扰它。 .NET GC非常具有攻击性,可以引用字段 x 在通过时,将不再计算“使用” x.Dispose() 线。 - porges
在你提到的Don Box书的第二页中,他使用了搜索算法的O(1)实现的例子,其“细节留给读者练习”。我笑了。 - wil


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

54
2018-02-11 18:42



我个人喜欢这个,但它并没有真正遵循框架设计指南。 - mquander
我认为这是正确的设计,因为它可以实现简单的确定性范围和范围构造/清理,尤其是在以复杂方式与异常处理,锁定和非托管资源使用块混合时。该语言将此作为一流的功能。 - yfeldblum
它并不完全遵循FDG中规定的规则,但它肯定是模式的有效使用,因为它是“使用声明”所使用的必需条件。 - Scott Dorman
只要Log.Outdent不扔,这绝对没有错。 - Daniel Earwicker
各种答案 是否滥用IDisposable和“使用”作为获取异常安全的“范围行为”的手段? 详细了解为什么不同的人喜欢/不喜欢这种技巧。这有点争议。 - Brian


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实际上甚至有可能立即释放内存的唯一一次是它处理disposing == false场景并操纵非托管资源。对于托管代码,内存实际上不会被回收,直到GC运行一个收集周期,你实际上无法控制(除了调用GC.Collect(),我已经提到过这不是一个好主意)。

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


36
2018-02-11 20:21



你没有忘记实施终结器吗? - Budda
@Budda:不,他正在使用SafeHandle。不需要析构函数。 - Henk Holterman
+1用于为Dispose()的多次调用添加安全网。规范说多次通话应该是安全的。太多的Microsoft类无法实现它,并且您会厌烦ObjectDisposedException。 - Jesse Chisholm
但Dispose(bool disposing)是你自己的SimpleCleanup类的方法,永远不会被框架调用。由于您只使用“true”作为参数调用它,因此“disposing”永远不会为false。您的代码与IDisposable的MSDN示例非常相似,但缺少终结器,正如@Budda指出的那样,disposing = false的调用将来自此处。 - yoyo


在调用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 (但仍然没有终结器,除非你已经实现了线程安全,这不是免费的)。


17
2018-02-11 19:31



re:“在调用Dispose之后,不应再调用对象的方法”。 “应该”是一个有效的词。如果您有异步操作挂起,它们可能会在您的对象被处置后进入。导致ObjectDisposedException。 - Jesse Chisholm
你的似乎是除了我的唯一答案,它触及了非托管资源封装了GC不理解的状态的想法。然而,非托管资源的一个关键方面是,即使“拥有”资源的对象不支持,其状态可能需要清理其状态的一个或多个实体也可以继续存在。你觉得我的定义怎么样?非常相似,但我认为它使“资源”更加名词化(它是外部对象改变其行为的“协议”,以换取不再需要其服务的通知) - supercat
@supercat - 如果您有兴趣,我在写完上述答案后几天写了以下帖子: smellegantcode.wordpress.com/2009/02/13/... - Daniel Earwicker
@DanielEarwicker:有趣的文章,虽然我可以想到至少有一种你没有真正涵盖的非托管资源:订阅来自长期存在的对象的事件。事件订阅是可替代的,但即使内存无限制地处理它们也可能是昂贵的。例如,允许在枚举期间进行修改的集合的枚举器可能需要订阅来自集合的更新通知,并且集合可能在其生命周期中多次更新。如果调查员被放弃而没有取消订阅...... - supercat
...订阅者名单将越来越大。即使内存是无限的,简单处理每个订阅请求的时间也可以不受限制地增长。我不认为你的模型非常适合这种模式,因为不清楚究竟什么是“分配”。从某种意义上说,事件订阅比非托管内存更具可替代性。另一方面,事件订阅确实有资格作为“对象已经要求另一个实体代表它执行的操作”。 - supercat


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

14
2018-02-11 18:20



完整的模式说明可以在 msdn.microsoft.com/en-us/library/b1yfkh5e.aspx - LicenseQ
除非你有非托管资源,否则不应该包含终结器。即便如此,首选实现是将非托管资源包装在SafeHandle中。 - Dave Black


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

我用 IDisposable 做一些事情,比如确保正确处理线程,以及非托管资源。

编辑 回应斯科特的评论:

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

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

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

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

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


11
2018-02-11 18:19



调用Dispose是完全有效,合法和鼓励的。实现IDisposable的对象通常会出于某种原因这样做。 GC性能指标受影响的唯一时间是调用GC.Collect()时。 - Scott Dorman
对于许多.net类来说,处理是“有些”可选的,这意味着只要不疯狂地创建新实例并放弃它们,“通常”放弃实例就不会造成任何麻烦。例如,编译器生成的控件代码似乎在实例化控件时创建字体,并在处理窗体时放弃它们;如果一个人创建和处理数千个控件,这可能会占用数千个GDI句柄,但在大多数情况下,控件不会被创建和销毁那么多。尽管如此,人们仍应尽量避免这种放弃。 - supercat
在字体的情况下,我怀疑问题是Microsoft从未真正定义哪个实体负责处理分配给控件的“font”对象;在某些情况下,控件可能与一个寿命较长的对象共享一个字体,因此使用控件Dispose the font会很糟糕。在其他情况下,一个字体将被分配给一个控件,而不是其他地方,所以如果控件没有处置它,没有人会。顺便说一句,如果有一个单独的非一次性FontTemplate类,可以避免使用字体的这种困难,因为控件似乎没有使用其Font的GDI句柄。 - supercat
关于可选主题 Dispose() 电话,见: stackoverflow.com/questions/913228/... - RJ Cuthbertson


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


10
2018-06-03 21:07



re:“当需要时,内存将被释放。”更确切地说,“当GC决定需要时。”在GC确定内存之前,您可能会看到系统性能问题 真 需要。释放它 现在 可能不是必需的,但可能有用。 - Jesse Chisholm
存在一些极端情况,其中对集合内的引用进行归零可以加速对由此引用的项目的垃圾收集。例如,如果创建了一个大型数组并填充了对较小的新创建项的引用,但在此之后很长时间内不需要它,放弃该数组可能会导致这些项保持到下一级2级GC,虽然首先将其归零可以使这些项目符合下一级0或1级GC的条件。可以肯定的是,大型物体堆上的大型短寿命物体无论如何都是icky(我不喜欢设计)但是... - supercat
...在放弃这些数组之前将其归零我有时会减少GC的影响。 - supercat


如果你想 现在删除, 使用 不受管理的记忆

看到:


7
2018-02-11 21:08





我不会重复关于使用或释放​​未管理资源的常规内容,这些内容已全部涵盖。但我想指出似乎是一种常见的误解。
给出以下代码

公共类LargeStuff
  实现IDisposable
  私有_Large为字符串()

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

  Public Sub Dispose()实现IDisposable.Dispose
    _Large =无
  结束子

我意识到Disposable实现不符合当前的指导原则,但希望你们都能理解。
现在,当调用Dispose时,释放多少内存?

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


6
2018-02-11 21:07



如果一个实例 LargeStuff 已经存在足够长的时间来进入第2代,如果 _Large 保存对第0代中新创建的字符串的引用,然后是实例 LargeStuff 被抛弃而没有废弃 _Large,然后字符串引用 _Large 将保留到下一代Gen2系列。归零 _Large 可以让字符串在下一代Gen0集合中被删除。在大多数情况下,将引用置零无效,但有些情况下它可以提供一些好处。 - supercat


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


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

我曾经使用IDisposable读取了一个简单的RollBack()故障代码。下面的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来创建更干净的代码。


5
2018-02-11 20:32



使用高阶函数查看@Daniel Earwicker的示例。对于基准测试,计时,日志记录等。它似乎更直接。 - Aluan Haddad


如果有的话,我希望代码是  离开时有效。

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


4
2018-06-15 13:56