我从阅读the 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
包含一百万个字符串,并且你想释放那个内存 now ,而不是等待垃圾收集器。上面的代码会实现吗?
答案 0 :(得分:2444)
答案 1 :(得分:57)
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.");
}
}
}
答案 2 :(得分:37)
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),它实际上在两种不同的情况下运行:
简单地让GC负责清理的问题是你无法真正控制GC何时运行一个收集周期(你可以调用GC.Collect(),但你真的不应该这样做)所以资源可能会比需要的时间更长。请记住,调用Dispose()实际上不会导致收集周期或以任何方式导致GC收集/释放对象;它只是提供了更加确定性地清理所用资源的方法,并告诉GC已经执行了这次清理。
IDisposable和处理模式的重点不在于立即释放内存。调用Dispose实际上甚至有可能立即释放内存的唯一一次是它处理disposing == false场景并操纵非托管资源。对于托管代码,内存实际上不会被回收,直到GC运行一个收集周期,你实际上无法控制(除了调用GC.Collect(),我已经提到过这不是一个好主意)。
由于.NET中的字符串不使用任何未管理的资源且未实现IDisposable,因此您的方案无效,因此无法强制它们被“清理”。
答案 3 :(得分:17)
在调用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
(但仍然没有终结器,除非你已经实现了线程安全,这不是免费的。)
答案 4 :(得分:14)
方案我使用IDisposable:清理非托管资源,取消订阅事件,关闭连接
我用来实现IDisposable的习惯用法( not threadsafe ):
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
}
答案 5 :(得分:11)
如果MyCollection
无论如何都要进行垃圾收集,那么你就不需要处理它了。这样做只会使CPU过度流失,甚至可能使垃圾收集器已经执行的一些预先计算的分析失效。
我使用IDisposable
来做一些事情,比如确保正确处理线程,以及非托管资源。
编辑回应Scott的评论:
GC性能指标受影响的唯一时间是调用[原文如此] GC.Collect()“
从概念上讲,GC维护对象引用图的视图,以及从线程堆栈帧对它的所有引用。这个堆可能非常大并且跨越许多页面的内存。作为优化,GC会缓存对不太可能经常更改的页面的分析,以避免不必要地重新扫描页面。当页面中的数据发生更改时,GC会从内核接收通知,因此它知道页面很脏并需要重新扫描。如果集合在Gen0中,那么页面中的其他内容可能也会发生变化,但这在Gen1和Gen2中的可能性较小。有趣的是,这些挂钩在Mac OS X中不适用于将GC移植到Mac以便在该平台上运行Silverlight插件的团队。
反对不必要的资源处置的另一点:想象一个进程正在卸载的情况。想象一下,这个过程已经运行了一段时间。有可能该进程的许多内存页面已被交换到磁盘。至少他们不再处于L1或L2缓存中。在这种情况下,卸载的应用程序没有必要将所有这些数据和代码页交换回内存,以“释放”当进程终止时将由操作系统释放的资源。这适用于托管甚至某些非托管资源。只有处理非后台线程活动的资源才能处理,否则进程将保持活动状态。
现在,在正常执行期间,必须正确清理临时资源(因为@fezmonkey指出数据库连接,套接字,窗口句柄)以避免非托管内存泄漏。这些是必须处理的事物。如果你创建了一个拥有一个线程的类(并且拥有我的意思是它创建它并因此负责确保它停止,至少通过我的编码风格),那么该类很可能必须实现IDisposable
并且撕裂在Dispose
期间关注主题。
.NET框架使用IDisposable
接口作为信号,甚至是警告,向开发人员发送此类必须。我无法想到框架中实现IDisposable
的任何类型(不包括显式接口实现),其中dispos是可选的。
答案 6 :(得分:11)
是的,该代码是完全冗余且不必要的,并且它不会使垃圾收集器做任何其他事情都不会做的事情(一旦MyCollection的实例超出范围,就是这样。)特别是{{1}电话。
回答你的编辑:排序。如果我这样做:
.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
。但是,这里没有理由这样做。在需要时将释放内存。
答案 7 :(得分:7)
答案 8 :(得分:6)
在您发布的示例中,它仍然没有“立即释放内存”。所有内存都是垃圾回收,但它可能允许在早期generation中收集内存。你必须运行一些测试才能确定。
框架设计指南是指南,而不是规则。它们告诉您接口的主要用途,何时使用,如何使用以及何时不使用它。
我曾经使用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来创建更清晰的代码。
答案 9 :(得分:6)
我不会重复关于使用或释放未受管理资源的常规内容,这些内容已全部涵盖。但我想指出一下似乎是一种常见的误解 鉴于以下代码
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,因此,内存将更快被重新声明。
添加finaliser来调用上面显示的Dispose方法没有意义。这将延迟重新声明内存以允许终结者运行。
答案 10 :(得分:5)
除了主要用于控制系统资源的生命周期 之外(完全由 Ian 的精彩答案涵盖,kudos !), IDisposable / using 组合也可用于范围(关键)全局资源的状态更改:控制台,< em> threads ,进程,任何全局对象,如应用程序实例。
我写了一篇关于这种模式的文章:http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/
它说明了如何以可重复使用和可读方式保护一些常用的全局状态:控制台颜色,当前线程文化, Excel应用程序对象属性 ...
答案 11 :(得分:4)
如果有的话,我希望代码 效率低于离开时的效率。
调用Clear()方法是不必要的,如果Dispose没有这样做,GC可能不会这样做......
答案 12 :(得分:3)
大多数“非托管资源”讨论的一个问题是它们并没有真正定义术语,但似乎暗示它与非托管代码有关。虽然许多类型的非托管资源确实与非托管代码进行交互,但在这些术语中考虑非托管资源是没有用的。
相反,人们应该认识到所有管理资源的共同点:它们都需要一个对象,要求某些外部“事物”代表它做某事,而不利于其他一些“事物”,另一个实体同意这样做直到另行通知。如果对象被抛弃并消失得无影无踪,那就没有什么可以告诉外面的“事物”它不再需要代表不再存在的对象来改变它的行为;因此,'事物的有用性将永久消失。然后,一个非托管资源代表一些外部“事物”的协议,以代表一个对象改变其行为,如果该对象被放弃并不再存在,那么这将无用地损害该外部“事物”的有用性。托管资源是这样一个协议的受益人,但是如果该协议被放弃则已经注册接收通知,并且在被销毁之前将使用此类通知将其事务整理好。
答案 13 :(得分:3)
Dispose()
操作在示例代码中有些事情可能具有不会因MyCollection
对象的正常GC而发生的影响。
如果_theList
或_theDict
引用的对象被其他对象引用,那么List<>
或Dictionary<>
对象将不会被收集但会突然出现没有内容。如果示例中没有Dispose()操作,那些集合仍将包含其内容。
当然,如果是这种情况,我会称之为破碎的设计 - 我只是指出(迂腐地,我想)Dispose()
操作可能不是完全多余的,这取决于是否有片段中未显示的List<>
或Dictionary<>
的其他用途。
答案 14 :(得分:2)
IDisposable
适合取消订阅活动。
答案 15 :(得分:2)
首先定义。对我来说,非托管资源意味着一些类,它实现了IDisposable接口或使用dll调用创建的东西。 GC不知道如何处理这些对象。如果类仅具有值类型,那么我不认为此类是具有非托管资源的类。 对于我的代码,我遵循下一步的做法:
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;
}
}
答案 16 :(得分:2)
处理受管资源的最合理用例是为GC准备回收原本无法收集的资源。
一个主要的例子是循环引用。
虽然最佳做法是使用避免循环引用的模式,但如果您最终使用(例如)“孩子”这样的话。如果您放弃引用并依赖GC,则可以停止对父级的GC集合,如果您已经实现了终结器,那么它可以停止父级的GC集合,它永远不会叫做。
解决此问题的唯一方法是通过在子项上将Parent引用设置为null来手动中断循环引用。
在父级和子级上实现IDisposable是执行此操作的最佳方式。在Parent上调用Dispose时,在所有子项上调用Dispose,在子Dispose方法中,将Parent references设置为null。
答案 17 :(得分:1)
您的代码示例不是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.
}
}
答案 18 :(得分:-1)
我看到很多答案转移到谈论将IDisposable用于托管和非托管资源。我建议将本文作为对IDisposable应如何实际使用的最佳解释之一。
https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About
对于实际问题;如果您使用IDisposable清理占用大量内存的托管对象,则简短的回答是否。原因是,一旦处置IDisposable,就应该让它超出范围。届时,所有引用的子对象也将超出范围,并将被收集。
唯一真正的例外是,如果您在托管对象中占用了大量内存,并且阻塞了该线程,等待某些操作完成。如果在该调用完成后不需要使用那些对象,则将这些引用设置为null可能会使垃圾收集器更快地收集它们。但是这种情况将代表需要重构的错误代码,而不是IDisposable的用例。