提问人:cwick 提问时间:2/12/2009 最后编辑:spaleetcwick 更新时间:4/9/2023 访问量:419479
正确使用 IDisposable 接口
Proper use of the IDisposable interface
问:
我从阅读 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.");
}
}
}
如果无论如何都要进行垃圾回收,那么您就不需要处理它。这样做只会使 CPU 的搅动超过必要的程度,甚至可能使垃圾回收器已经执行的某些预先计算的分析无效。MyCollection
我过去常常做一些事情,比如确保线程以及非托管资源被正确处置。IDisposable
编辑针对斯科特的评论:
GC 性能指标唯一受到影响的情况是调用 [sic] GC.Collect() 已生成”
从概念上讲,GC 维护对象引用图的视图,以及线程堆栈帧中对它的所有引用。此堆可能非常大,并且跨越许多内存页。作为优化,GC 会缓存其对不太可能经常更改的页面的分析,以避免不必要地重新扫描页面。当页面中的数据发生更改时,GC 会收到来自内核的通知,因此它知道页面是脏的,需要重新扫描。如果集合位于 Gen0 中,则页面中的其他内容也可能发生更改,但在 Gen1 和 Gen2 中不太可能发生更改。有趣的是,这些钩子在 Mac OS X 中不可用,用于将 GC 移植到 Mac 的团队,以便让 Silverlight 插件在该平台上工作。
反对不必要处置资源的另一点:想象一个过程正在卸载的情况。想象一下,该过程已经运行了一段时间。该进程的许多内存页可能已交换到磁盘。至少它们不再位于 L1 或 L2 缓存中。在这种情况下,正在卸载的应用程序没有必要将所有这些数据和代码页交换回内存中,以“释放”在进程终止时操作系统无论如何都会释放的资源。这适用于托管资源,甚至某些非托管资源。只有使非后台线程保持活动状态的资源才必须被释放,否则进程将保持活动状态。
现在,在正常执行期间,必须正确清理一些临时资源(如@fezmonkey指出的数据库连接、套接字、窗口句柄),以避免非托管内存泄漏。这些都是必须处理的东西。如果你创建了一个拥有线程的类(我所说的拥有是指它创建了它,因此负责确保它停止,至少按照我的编码风格),那么该类很可能必须在 .IDisposable
Dispose
.NET Framework 使用该接口作为信号,甚至警告开发人员必须释放此类。我想不出框架中实现(不包括显式接口实现)的任何类型,其中处置是可选的。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),它实际上在两种不同的环境中运行:
- Dispose == true:该方法已被用户代码直接或间接调用。可以释放托管和非托管资源。
- Dispose == false:该方法已由运行时从终结器内部调用,不应引用其他对象。只能释放非托管资源。
简单地让 GC 负责清理的问题在于,您无法真正控制 GC 何时运行收集周期(您可以调用 GC.Collect(),但你真的不应该),所以资源可能会比需要的时间更长。请记住,调用 Dispose() 实际上不会导致收集周期,也不会以任何方式导致 GC 收集/释放对象;它只是提供了更确定地清理所用资源的方法,并告诉 GC 已经执行了此清理。
IDisposable 和 dispose 模式的重点不是立即释放内存。对 Dispose 的调用实际上甚至有机会立即释放内存的唯一时间是当它处理 dispose == false 场景并操作非托管资源时。对于托管代码,在 GC 运行收集周期之前,内存实际上不会被回收,您实际上无法控制(除了调用 GC.Collect(),我已经提到过这不是一个好主意)。
你的方案实际上并不有效,因为 .NET 中的字符串不使用任何未识别的资源,也不实现 IDisposable,因此无法强制“清理”它们。
评论
该操作在示例代码中执行的某些操作可能会产生由于对象的正常 GC 而不会发生的效果。Dispose()
MyCollection
如果对象被其他对象引用或被其他对象引用,则该对象将不受收集的约束,但会突然没有内容。如果没有示例中的 Dispose() 操作,这些集合仍将包含其内容。_theList
_theDict
List<>
Dictionary<>
当然,如果是这种情况,我会称其为破碎的设计 - 我只是指出(我想是迂腐的)该操作可能不是完全多余的,具体取决于是否有其他用途 or 未在片段中显示。Dispose()
List<>
Dictionary<>
评论
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()
注意:它们不会泄漏托管资源,因为垃圾回收器最终将在后台线程上运行,并释放与任何未使用对象关联的内存。这将包括您的对象以及您使用的任何托管对象(例如 和 )。
Bitmap
DbConnection
如果这个人忘了打电话,我们仍然可以保存他们的培根!我们仍然有一种方法可以调用它们:当垃圾收集器最终开始释放(即最终确定)我们的对象时。Dispose()
注意:垃圾回收器最终将释放所有托管对象。 当它这样做时,它会对对象调用
Finalize
方法。GC 不知道,或者 关心你的 Dispose 方法。 这只是我们选择的一个名字 当我们想要获取时调用的方法 摆脱不受管理的东西。
垃圾回收器对我们的对象的破坏是释放那些讨厌的未管理资源的最佳时机。我们通过重写方法来实现这一点。Finalize()
注意:在 C# 中,不会显式重写该方法。 您编写了一个看起来像 C++ 析构函数的方法,并且 编译器将其视为该方法的实现:
Finalize()
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()
执行此操作的标准模式是 have 并且 both 调用 third(!) 方法;如果你从(而不是 )调用布尔值,则传递布尔值,这意味着释放托管资源是安全的。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();
}
}
一切都很好,除了你可以做得更好!
如果用户调用您的对象,则所有内容都已清理。稍后,当垃圾回收器出现并调用 Finalize 时,它将再次调用。Dispose()
Dispose
这不仅浪费,而且如果您的对象对上次调用时已经释放的对象有垃圾引用,您将尝试再次释放它们!Dispose()
您会注意到,在我的代码中,我小心翼翼地删除了对我已处置的对象的引用,因此我不会尝试调用垃圾对象引用。但这并没有阻止一个微妙的错误悄悄出现。Dispose
当用户调用时:句柄 CursorFileBitmapIconServiceHandle 被销毁。稍后,当垃圾回收器运行时,它将尝试再次销毁相同的句柄。Dispose()
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 的第一章:
- 直接链接:Pearson Publishing 的第 1 章样本
- 磁铁:84BF0B960936D677190A2BE355858E80EF7542C0
在35页的篇幅中,他解释了使用二进制对象的问题,并在你眼前发明了COM。一旦你意识到COM的原因,剩下的300页是显而易见的,只是详细介绍了Microsoft的实现。
我认为每个曾经处理过对象或COM的程序员都应该至少阅读第一章。这是对任何事情的最好解释。
额外奖励阅读
当你所知道的一切都是错误的档案埃里克·利珀特
因此,写一个正确的终结器确实非常困难, 我能给你的最好的建议就是不要尝试。
评论
.Dispose
using
Dispose
Dispose
.Dispose
.Dispose
我使用 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 管理。
这些函数可能会导致状态更改,这些更改可以自由交错,或者可能需要完全嵌套。状态更改可能是线程安全的,也可能不是。
看看贾斯蒂斯问题中的例子。对日志文件缩进的更改必须完全嵌套,否则一切都会出错。此外,它们不太可能是线程安全的。
可以搭上垃圾回收器的顺风车,清理非托管资源。但前提是状态更改函数是线程安全的,并且两个状态的生存期可以以任何方式重叠。因此,正义的资源示例一定不能有终结者!它只是对任何人都无济于事。
对于这些类型的资源,您可以只实现 ,而无需终结器。终结器是绝对可选的 - 它必须是。这在许多书中都被掩盖了,甚至没有提到。IDisposable
然后,您必须使用该语句来确保它被调用。这基本上就像搭上堆栈的顺风车(因此,终结器之于 GC,即堆栈)。using
Dispose
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
如果有的话,我希望代码的效率低于将其排除在外时。
调用 Clear() 方法是不必要的,如果 Dispose 不这样做,GC 可能不会这样做......
在您发布的示例中,它仍然没有“立即释放内存”。所有内存都是垃圾回收的,但它可能允许在上一代中收集内存。您必须运行一些测试才能确定。
框架设计指南是指南,而不是规则。它们告诉您界面的主要用途、何时使用它、如何使用它以及何时不使用它。
我曾经读过一个简单的代码 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 创建更简洁的代码。
评论
我不会重复关于使用或释放非托管资源的通常内容,这些内容都已涵盖。但我想指出一个似乎很普遍的误解。
给定以下代码
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
我意识到 Disposable 实现没有遵循当前的准则,但希望你们都能理解这个想法。
现在,当调用 Dispose 时,会释放多少内存?
答:没有。
调用 Dispose 可以释放非托管资源,它不能回收托管内存,只有 GC 可以这样做。这并不是说上述不是一个好主意,实际上遵循上述模式仍然是一个好主意。运行 Dispose 后,即使 LargeStuff 实例可能仍在范围内,也没有什么可以阻止 GC 重新回收_Large正在使用的内存。_Large 中的字符串也可能在第 0 代中,但 LargeStuff 的实例可能是第 2 代,因此,内存将很快被回收。
但是,添加终结器来调用上面所示的 Dispose 方法是没有意义的。这只会延迟重新回收内存以允许终结器运行。
评论
LargeStuff
_Large
LargeStuff
_Large
_Large
_Large
IDisposable
适用于取消订阅事件。
大多数关于“非托管资源”的讨论的一个问题是,它们并没有真正定义这个术语,但似乎暗示它与非托管代码有关。虽然许多类型的非托管资源确实与非托管代码交互,但从这些术语中考虑非托管资源是没有帮助的。
相反,人们应该认识到所有托管资源的共同点:它们都要求某个外部“事物”代表它做某事,从而损害其他一些“事物”,而另一个实体同意这样做,直到另行通知。如果这个物体被遗弃并消失得无影无踪,那么在“事物”之外,没有什么东西会告诉它,它不再需要代表不再存在的物体改变它的行为;因此,“东西”的有用性将被永久削弱。
因此,非托管资源代表了某个外部“事物”代表对象改变其行为的协议,如果该对象被放弃并不再存在,这将无用地损害该外部“事物”的有用性。托管资源是此类协议的受益人,但已签署在被遗弃时接收通知的对象,并将使用此类通知在销毁之前整理其事务。
评论
如果要立即删除,请使用非托管内存。
看:
除了主要用作控制系统资源生存期的一种方式(完全包含在 Ian 的精彩回答中,荣誉!)之外,IDisposable/using 组合还可用于确定(关键)全局资源的状态更改范围:控制台、线程、进程、任何全局对象(如应用程序实例)。
我写了一篇关于这种模式的文章: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
以下模板演示了我用文字描述的代码示例:
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;
}
}
评论
is IDisposable
处置托管资源的最合理用例是准备 GC 回收否则永远不会收集的资源。
一个典型的例子是循环引用。
虽然最佳做法是使用避免循环引用的模式,但如果您最终得到(例如)一个“子”对象,该对象具有对其“父”的引用,如果您只是放弃引用并依赖 GC,这可能会停止父对象的 GC 收集 - 此外,如果您已经实现了终结器,则永远不会调用它。
解决此问题的唯一方法是通过将子项上的父项引用设置为 null 来手动中断循环引用。
在父级和子级上实现 IDisposable 是执行此操作的最佳方法。当对 Parent 调用 Dispose 时,对所有 Children 调用 Dispose,并在子 Dispose 方法中,将 Parent 引用设置为 null。
评论
WeakReference
给定的代码示例不是一个很好的使用示例。字典清除通常不应转到该方法。当字典项目超出范围时,将清除并释放字典项目。 需要实现来释放一些内存/处理程序,这些内存/处理程序即使在超出范围后也不会释放/释放。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 的用例。
评论
我认为人们将 IDisposable 的 PATTERN 与 IDisposable 的主要目的混为一谈,后者旨在帮助清理非托管资源。我们都知道这一点。有些人认为这种模式具有某种神奇的力量,可以清除记忆并释放资源。PATTERN 不会这样做。但是,将该模式与实现的方法一起使用确实会清除内存并释放资源。
该模式只是一个内置的 try{} finally{} 块。而已。仅此而已。那么这意味着什么呢?您可以创建一个代码块,让您在最后执行某些操作,而无需为其执行额外的代码。它提供了一个 CUSTOM 块,可用于对代码和范围进行分段。
我的例子:
//My way
using (var _ = new Metric("My Test"))
{
DoSomething(); //You now know all work in your block is being timed.
}
//MS mockup from memory
var sw = new Stopwatch();
sw.Start();
DoSomething(); //something fails? I never get the elapsed time this way
sw.Stop();
Metric 类
public class Metric : IDisposable
{
private string _identifier;
private DateTime _start;
public Metric(string identifier)
{
_identifier = identifier;
_start = DateTime.Now;
}
public void Dispose()
{
Console.WriteLine(_identifier + " - " + (DateTime.Now - _start).TotalMilliseconds)
}
}
评论
IDisposable
不标记任何东西。该方法执行它必须执行的操作来清理实例使用的资源。这与GC无关。Dispose
IDisposable
IDisposable