我通过阅读 Microsoft documentation 知道 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
包含一百万个字符串,并且您希望现在释放该内存,而不是等待垃圾收集器。上面的代码能做到这一点吗?
IDisposable
不标记任何内容。 Dispose
方法执行它必须做的事情来清理实例使用的资源。这与GC无关。
IDisposable
。这就是为什么我说接受的答案没有回答 OP 关于 IDisposable 是否有助于 <i>释放内存</i> 的预期问题(和后续编辑)。由于 IDisposable
与释放内存无关,只有资源,所以就像你说的那样,根本不需要将托管引用设置为 null ,这就是 OP 在他的示例中所做的。所以,他的问题的正确答案是“不,它不能更快地释放内存。事实上,它根本不能帮助释放内存,只有资源”。但无论如何,感谢您的意见。
Dispose 的目的是释放非托管资源。它需要在某个时候完成,否则它们将永远不会被清理干净。垃圾收集器不知道如何对 IntPtr
类型的变量调用 DeleteHandle()
,它不知道是否需要调用 {1 }。
注意:什么是非托管资源?如果您在 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 的内存就坐在那里 - 等待垃圾收集器最终出现并释放它吗?如果有 open database connection 怎么办?当然,我们不希望该连接处于打开状态,等待 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()
,我们仍然可以保存他们的培根!我们仍然可以将其称为 for 他们:当垃圾收集器最终开始释放(即最终确定)我们的对象时。
注意:垃圾收集器最终将释放所有托管对象。当它这样做时,它会调用对象的 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()
代码中,您尝试摆脱的 managed 对象(因为您想提供帮助)完全有可能不再存在:
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()
都调用 third(!) 方法;如果您从 Dispose()
(而不是 Finalize()
)调用它,则传递一个布尔值,这意味着释放托管资源是安全的。
这个internal方法可以被赋予一些任意名称,例如“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
。
这不仅浪费,而且如果您的对象对您在 last 调用 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
:
执行与释放、释放或重置非托管资源相关的应用程序定义任务。
那么它是哪一个?哪一个是我清理非托管资源的地方?答案是:
这是你的选择!但选择处置。
您当然可以将非托管清理放在终结器中:
~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 页就很明显了,只是详细介绍了微软的实现。
我认为每个处理过对象或 COM 的程序员至少应该阅读第一章。这是对任何事情的最好解释。
额外的奖金阅读
When everything you know is wrong archive埃里克·利珀特
因此,编写一个正确的终结器确实非常困难,我能给你的最好建议是不要尝试。
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.");
}
}
}
Dispose 模式的目的是提供一种机制来清理托管和非托管资源,何时发生取决于调用 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),它实际上在两种不同的情况下运行:
disposing == true:该方法已被用户代码直接或间接调用。可以处置托管和非托管资源。
disposing == false:该方法已被运行时从终结器内部调用,您不应引用其他对象。只能释放非托管资源。
简单地让 GC 负责清理的问题在于,您无法真正控制 GC 何时运行收集周期(您可以调用 GC.Collect(),但实际上不应该),因此资源可能会保留大约比需要的时间长。请记住,调用 Dispose() 实际上不会导致收集周期或以任何方式导致 GC 收集/释放对象;它只是提供了一种更确定性地清理所用资源的方法,并告诉 GC 已经执行了此清理。
IDisposable 和 dispose 模式的重点不是立即释放内存。对 Dispose 的调用实际上甚至有机会立即释放内存的唯一一次是在它处理 disposing == false 场景和操作非托管资源时。对于托管代码,内存实际上不会被回收,直到 GC 运行一个收集周期,你真的无法控制(除了调用 GC.Collect(),我已经提到这不是一个好主意)。
您的方案实际上并不有效,因为 .NET 中的字符串不使用任何未管理的资源并且不实现 IDisposable,因此无法强制“清理”它们。
在对对象调用 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
(但仍然没有终结器,除非你已经实现了线程安全,它不是免费的)。
我使用 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
}
是的,该代码是完全多余和不必要的,它不会使垃圾收集器做任何它不会做的事情(一旦 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()
。不过,这里没有理由这样做。内存将在需要时被释放。
如果无论如何都要对 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
的任何类型(不包括显式接口实现),其中处置是可选的。
在您发布的示例中,它仍然没有“立即释放内存”。所有内存都被垃圾收集,但它可能允许在更早的 generation 中收集内存。您必须进行一些测试才能确定。
框架设计指南是指南,而不是规则。它们会告诉您界面的主要用途、何时使用、如何使用以及何时不使用。
我曾经读过代码,它是一个简单的 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 后,没有什么可以阻止 GC 重新声明 _Large 正在使用的内存,即使 LargeStuff 的实例可能仍在范围内。 _Large 中的字符串也可能在 gen 0 中,但 LargeStuff 的实例可能在 gen 2 中,所以同样,内存会被更快地回收。但是,添加终结器来调用上面显示的 Dispose 方法是没有意义的。这只会延迟内存的重新声明以允许终结器运行。
LargeStuff
的实例已经存在足够长的时间以使其进入第 2 代,并且如果 _Large
持有对第 0 代中新创建的字符串的引用,那么如果 LargeStuff
的实例被放弃如果不清空 _Large
,则 _Large
引用的字符串将保留到下一个 Gen2 集合。清零 _Large
可能会使字符串在下一个 Gen0 集合中被消除。在大多数情况下,取消引用没有帮助,但在某些情况下它可以提供一些好处。
如果要立即删除,请使用非托管内存。
看:
Marshal.AllocHGlobal
元帅.FreeHGlobal
Marshal.DestroyStructure
如果有的话,我希望代码的效率低于将其排除在外时的效率。
调用 Clear() 方法是不必要的,如果 Dispose 没有这样做,GC 可能不会这样做......
除了作为控制系统资源生命周期的主要用途(完全被 Ian 的精彩回答所涵盖,赞!)之外,IDisposable/using 组合还可用于确定(关键)全局资源的状态更改范围:控制台、线程、进程、任何全局对象,如应用程序实例。
我写过一篇关于这种模式的文章:http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/
它说明了如何以可重用和可读的方式保护一些常用的全局状态:控制台颜色、当前线程文化、Excel 应用程序对象属性......
我看到很多答案已经转向谈论将 IDisposable 用于托管和非托管资源。我建议这篇文章作为我找到的关于 IDisposable 应该如何实际使用的最佳解释之一。
https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About
对于实际问题;如果您使用 IDisposable 来清理占用大量内存的托管对象,那么简短的回答是否定的。原因是一旦持有内存的对象超出范围,它就可以被收集了。此时,任何引用的子对象也超出范围并将被收集。
唯一真正的例外是,如果您在托管对象中占用了大量内存,并且您已阻止该线程等待某些操作完成。如果在该调用完成后不再需要那些对象,那么将这些引用设置为 null 可能允许垃圾收集器更快地收集它们。但这种情况将代表需要重构的错误代码——而不是 IDisposable 的用例。
您给定的代码示例不是 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.
}
}
Dispose()
操作在示例代码中执行的某些操作可能具有由于 MyCollection
对象的正常 GC 而不会发生的效果。
如果 _theList
或 _theDict
引用的对象被其他对象引用,则该 List<>
或 Dictionary<>
对象将不会被收集,而是突然没有内容。如果没有像示例中那样的 Dispose() 操作,这些集合仍将包含它们的内容。
当然,如果是这种情况,我会称其为损坏的设计 - 我只是指出(我认为是迂腐的)Dispose()
操作可能不是完全多余的,这取决于 { 是否还有其他用途2} 或 Dictionary<>
未在片段中显示。
大多数关于“非托管资源”的讨论的一个问题是他们并没有真正定义这个术语,但似乎暗示它与非托管代码有关。虽然许多类型的非托管资源确实与非托管代码交互,但以这种方式考虑非托管资源并没有帮助。
相反,人们应该认识到所有托管资源的共同点:它们都需要一个对象要求一些外部“事物”代表它做某事,这会损害一些其他“事物”,并且另一个实体同意这样做,直到另行通知。如果该对象被遗弃并消失得无影无踪,那么没有任何东西可以告诉外部“事物”它不再需要代表不再存在的对象改变其行为;因此,这件东西的用处将永久减少。
因此,非托管资源代表某个外部“事物”同意代表对象改变其行为,如果该对象被放弃并不再存在,这将无用地损害该外部“事物”的有用性。托管资源是一个对象,它是这种协议的受益者,但如果它被放弃,它已经签约接收通知,并且在它被销毁之前将使用这种通知来整理它的事务。
IDisposable
适用于取消订阅事件。
首先是定义。对我来说,非托管资源意味着一些类,它实现了 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 时,对所有 Child 调用 Dispose,并在子 Dispose 方法中,将 Parent 引用设置为 null。
WeakReference
的目标的每个对象,系统将检查一个标志,该标志指示在最后一个 GC 周期,并将对象添加到需要立即终结的对象队列中,从大对象堆中释放对象,或者使弱引用无效。如果不存在其他引用,则循环引用不会使对象保持活动状态。
不定期副业成功案例分享
Dispose()
调用期间将托管实例设置为 null 有什么影响,除了确保它们不会因为!= null
检查失败而再次被释放?不是Disposable
的托管类型怎么办?是否应该在Dispose
方法中处理它们(例如设置为 null)?应该对所有托管对象执行此操作,还是仅对那些我们认为“重”且值得在 GC 启动之前进行任何操作的对象执行此操作?我希望它仅适用于类的Disposable
成员,但作为示例提到的system.Drawing.Image
似乎不是一次性的......Dispose
方法中将任何您喜欢的变量设置为null
。将变量设置为null
意味着它只会可能更快地被收集(因为它没有未完成的引用)。如果一个对象没有实现IDisposable
,那么您不必处置它。只有当对象需要被释放时,它才会公开Dispose
。.Dispose
。有时您不能使用using
。我们遵循“成功之坑” 的.NET/WinRT 方法。我们为开发人员缴纳税款,并编写更好的防御性代码以使其能够应对这些问题。