关于继承IDisposable的Microsoft构建类,我是否必须显式调用Dispose以防止内存泄漏?
我理解最好调用Dispose(或者更好地使用using块),但是在编程时,我并不总是立即意识到一个类继承自IDisposable。
我也理解微软IDisposable的实现有点过时,这就是他们创建the article explaining the correct usage of IDisposable的原因。
长话短说,在哪些情况下可以忘记调用Dispose?
答案 0 :(得分:10)
主要问题中有几个问题
我是否必须显式调用Dispose以防止内存泄漏?
在实施Dispose
的任何类型上调用IDisposable
是非常推荐的,甚至可能是类型合同的基本部分。完成对象后几乎没有理由不调用Dispose
。意图处置IDisposable
对象。
但是无法调用Dispose会造成内存泄漏吗?有可能。它非常依赖于该对象在Dispose方法中的作用。许多免费记忆,一些从事件中解脱,其他人免费处理等等......它可能不会泄漏记忆,但几乎肯定会对你的程序产生负面影响
在哪些情况下可以忘记调用Dispose?
我从没有开始。绝大多数对象出于正当理由实现IDisposable
。未能致电Dispose
会损害您的计划。
答案 1 :(得分:4)
这取决于两件事:
处理功能
Dispose可以执行多种操作,例如关闭资源句柄(如文件流),更改类状态并释放类本身使用的其他组件。
在资源被释放的情况下(如文件),在显式调用它和等待垃圾收集期间调用它之间存在功能差异(假设终结器调用dispose)。
如果没有状态更改且只释放组件,则不会有内存泄漏,因为稍后GC将释放该对象。
<强>终结强>
在大多数情况下,一次性类型从终结器调用Dispose方法。如果是这种情况,并假设调用dispose的上下文无关紧要,那么如果对象不会被明确处理,您很可能会注意到没有区别。但是,如果没有从终结器中调用Dispose,那么您的代码将表现不同。
底线 - 在大多数情况下,最好在完成后明确处理对象。
一个简单的例子,最好是明确地调用Dispose:假设你使用FileStream来编写一些内容并且不启用共享,那么该文件会被进程锁定,直到GC获取对象为止。该文件也可能无法将所有内容刷新到文件中,因此如果进程在写入结束后的某个时刻崩溃,则无法保证它实际上会被保存。
答案 2 :(得分:3)
可以安全不要拨打Dispose
,但问题是知道何时出现这种情况。
好的95%的IEnumerator<T>
实施都有Dispose
可以安全忽略,但5%不仅仅是导致错误的5%,而是5 %会导致难以追踪的bug。更重要的是,通过IEnumerator<T>
的代码将同时看到95%和5%,并且无法动态地将它们分开(它可以实现非没有实施IEnumerable
的通用IDisposable
,以及MS决定让IEnumerator<T>
继承IDisposable
来猜测结果如何!)
其余的,可能有3%或4%的时间是安全的。目前。如果没有查看代码,你就不知道哪3%,即使这样,合同也说你必须调用它,所以开发人员可以依赖你这样做,如果他们发布一个重要的新版本。
总之,始终致电Dispose()
。 (我可以想到一个例外,但是坦率地说甚至太过于讨论细节了,在这种情况下调用它仍然是安全的,只是不重要)。
关于自己实施IDisposable
的问题,避免使用那些被诅咒的文件中的模式。
我认为这种模式是一种反模式。这是一个很好的模式,用于在包含托管和非托管资源的类中实现IDisposable.Dispose
和终结器。但是,首先持有托管IDisposable
和非托管资源是一个坏主意。
相反:
如果您拥有非托管资源,则不要使用任何实施IDisposable
的非托管资源。现在Dispose(true)
和Dispose(false)
代码路径是相同的,所以它们真的可以成为:
public class HasUnmanaged : IDisposable
{
IntPtr unmanagedGoo;
private void CleanUp()
{
if(unmanagedGoo != IntPtr.Zero)
{
SomeReleasingMethod(unmanagedGoo);
unmanagedGoo = IntPtr.Zero;
}
}
public void Dispose()
{
CleanUp();
GC.SuppressFinalize(this);
}
~HasUnmanaged()
{
CleanUp();
}
}
如果您有需要处理的托管资源,那么就这样做:
public class HasUnmanaged : IDisposable
{
IDisposable managedGoo;
public void Dispose()
{
if(managedGoo != null)
managedGoo.Dispose();
}
}
在那里,没有神秘的&#34;处理&#34; bool(怎样才能称为Dispose
并将false
用于disposing
?)在99.99%的时间内你不需要担心终结者需要它们(第二种模式比第一种模式更常见)。一切都好。
真的需要既有托管资源又有非托管资源的东西?不,你不是真的,将非托管资源包装在你自己的一个类中,作为它的句柄,然后该句柄适合上面的第一个模式,主类适合第二个模式。
只有在您被强制执行时才实现CA10634模式,因为您继承了这样做的类。值得庆幸的是,大多数人都不会再创建新的了。
答案 3 :(得分:1)
永远不会忘记拨打Dispose
(或者,正如您所说,最好还是使用using
)。
我想如果你的程序的目标是导致非托管资源泄漏。那么也许没关系。
答案 4 :(得分:0)
IDisposable
的实现表明一个类使用未受管理的资源。当您确定已完成课程时,应始终致电Dispose()
(或尽可能使用using
阻止)。否则,您将不必要地分配未管理的资源。
换句话说,永远不要忘记致电Dispose()
。
答案 5 :(得分:0)
是的,总是打电话处理。显式或隐式(通过使用)。以,例如,Timer类。如果你没有明确地停止计时器,并且不处理它,那么它将继续射击,直到垃圾收集器到处收集它。这实际上可能导致崩溃或意外行为。
最好确保在完成后立即调用Dispose。
答案 6 :(得分:0)
微软(可能不正式)表示在某些情况下不能调用Dispose。
来自Microsoft writes的Stephen Toub(关于调用Dispose on Task):简而言之,就像在.NET中的情况一样,如果,则积极地处理 它很容易,并根据您的代码结构更正。 如果 你开始做奇怪的旋转以便处理(或在 在Tasks的情况下,使用额外的同步来确保它是安全的 处置,因为Dispose只能在任务完成后使用), 最好依靠终结来处理事情。 最后,最好测量,测量,测量,看看你是否真的 在你尽量减少代码之前遇到问题 用于实现清理功能。
[大胆强调是我的]
另一种情况是基本流
var inner = new FileStrem(...);
var outer = new StreamReader(inner, Encoding.GetEncoding(1252));
...
outer.Dispose();
inner.Dispose(); -- this will trigger a FxCop performance warning about calling Dispose twice.
(我已关闭此规则)