对于Microsoft构建的继承IDisposable的类,我是否必须显式调用Dispose?

时间:2012-01-12 16:06:58

标签: c# .net dispose idisposable

关于继承IDisposable的Microsoft构建类,我是否必须显式调用Dispose以防止内存泄漏?

我理解最好调用Dispose(或者更好地使用using块),但是在编程时,我并不总是立即意识到一个类继承自IDisposable。

我也理解微软IDisposable的实现有点过时,这就是他们创建the article explaining the correct usage of IDisposable的原因。

长话短说,在哪些情况下可以忘记调用Dispose?

7 个答案:

答案 0 :(得分:10)

主要问题中有几个问题

  

我是否必须显式调用Dispose以防止内存泄漏?

在实施Dispose任何类型上调用IDisposable是非常推荐的,甚至可能是类型合同的基本部分。完成对象后几乎没有理由不调用Dispose。意图处置IDisposable对象。

但是无法调用Dispose会造成内存泄漏吗?有可能。它非常依赖于该对象在Dispose方法中的作用。许多免费记忆,一些从事件中解脱,其他人免费处理等等......它可能不会泄漏记忆,但几乎肯定会对你的程序产生负面影响

  

在哪些情况下可以忘记调用Dispose?

我从没有开始。绝大多数对象出于正当理由实现IDisposable。未能致电Dispose会损害您的计划。

答案 1 :(得分:4)

这取决于两件事:

  1. Dispose方法中会发生什么
  2. 终结器是否调用Dispose
  3. 处理功能
    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.

(我已关闭此规则)