5

我正在努力确保我的理解IDisposable是正确的,并且有些事情我仍然不太确定。

IDisposable似乎有两个目的。

  1. 提供按需“关闭”托管对象的约定。
  2. 提供一个约定以释放托管对象持有的“非托管资源”。

我的困惑来自于确定哪些场景有“非托管资源”在起作用。

假设您正在使用 Microsoft 提供的IDisposable实现(托管)类(例如,与数据库或套接字相关的)。

  1. 你怎么知道它是IDisposable只为上面的1还是1&2实现的?
  2. 您是否负责确保释放内部可能持有或不持有的非托管资源?您是否应该向您自己的调用 instanceOfMsSuppliedClass.Dispose() 的类添加终结器(这将是正确的机制吗?)?
4

8 回答 8

10
  1. 你怎么知道它是为上面的 1 还是 1&2 实现 IDisposable ?

你的第一个问题的答案是“你不应该知道”。如果您使用的是第三方代码,那么您在某种程度上是它的怜悯 - 当您调用 Dispose 时,您必须相信它会正确处理自己。如果你不确定或者你认为有错误,你总是可以尝试使用 Reflector() 来反汇编它(如果可能的话)并检查它在做什么。

  1. 我是否负责确保释放内部可能持有或不持有的非托管资源?我是否应该向我自己的调用 instanceOfMsSuppliedClass.Dispose() 的类添加终结器(这将是正确的机制吗?)?

如果您使用的是 .Net 2.0 或更高版本,您应该很少(如果有的话)需要为您的类实现终结器。终结器会为您的类增加开销,并且通常不会提供比仅实现 Dispose 所需的更多功能。我强烈建议您访问这篇文章,以获得有关正确处理的一个很好的概述。在您的情况下,您可能希望调用instanceofMSSuppliedClass.Dispose()自己的 Dispose() 方法。

归根结底,对对象调用 Dispose() 是一种很好的做法,因为它明确地让 GC 知道你已经完成了资源并允许用户立即清理它,并通过让其他程序员知道间接记录代码该对象此时已使用资源完成。但是即使您忘记显式调用它,它最终也会在对象无根时发生(毕竟.Net 是一个托管平台)。仅当您的对象具有需要隐式清理的非托管资源时才应实现终结器(即,消费者有可能忘记清理它,这将是有问题的)。

于 2009-06-18T15:22:36.017 回答
5

您应该始终对实现 IDisposable 的对象调用 Dispose(除非他们明确告诉您这是一个有用的约定,例如 ASP.NET MVC 的 HtmlHelper.BeginForm)。您可以使用“使用”语句来简化此操作。如果您将类中 IDisposable 的引用作为成员字段挂起,那么您应该使用Disposable Pattern来实现 IDisposable 以清理这些成员。如果您运行像 FxCop 这样的静态分析工具,它会告诉您相同的结果。

你不应该试图猜测界面。今天,该类可能不会使用非托管资源,但下一个版本呢?

于 2009-06-18T15:21:49.653 回答
1

您不对对象的内容负责。Dispose() 应该是透明的,并且释放它需要释放的东西。之后,您无需为此负责。

非托管资源是您在(托管)C++ 中创建的资源,您可以在其中通过指针和“new”语句而不是“gcnew”语句分配内存。当您在 C++ 中创建一个类时,您有责任删除此内存,因为它是本机内存或非托管内存,而垃圾收集器不关心它。您还可以通过 Marshal 分配创建此非托管内存,并且我假设是不安全的代码。

使用托管 C++ 时,您也不必手动实现 IDisposable 类。当您编写解构器时,它将被编译为 Dispose() 函数。

于 2009-06-18T15:23:08.667 回答
1

如果有问题的类是 Microsoft 提供的(即数据库等),那么 Dispose 的处理(来自 IDisposable)很可能已经被处理了,这取决于你来调用它。例如,使用数据库的标准做法如下所示:

//...
using (IDataReader dataRead = new DataReaderObject())
{
   //Call database
}

这与写作基本相同:

IDataReader dataRead = null;
try
{
    dataRead = new DataReaderObject()
    //Call database
}
finally
{
    if(dataRead != null)
    {
        dataRead.Dispose();
    }
}

据我了解,在从 IDisposable 继承的对象上使用前者通常是一种好习惯,因为它将确保正确释放资源。

至于自己使用 IDisposable ,实现由您决定。从它继承后,您应该确保该方法包含处理您可能手动创建的任何数据库连接所需的代码,释放可能保留的资源或防止对象被破坏,或者只是清理大型资源池(如图像)。这也包括非托管资源,例如,标记在“不安全”块内的代码本质上是非托管代码,可以允许直接内存操作,这肯定需要清理。

于 2009-06-18T15:35:37.830 回答
1

术语“非托管资源”有点用词不当。基本概念是成对动作的概念——执行一个动作会产生执行某些清理动作的需要。打开文件需要关闭它。拨调制解调器需要挂机。系统可能会在未能执行清理操作的情况下幸存下来,但后果可能很严重。

当一个对象被称为“持有非托管资源”时,真正的意思是该对象具有对其他实体执行某些必需的清理操作所需的信息和动力,并且没有特别的理由相信信息和动力存在于任何地方别的。如果唯一拥有这些信息和动力的对象被完全抛弃,则永远不会发生所需的清理操作。.Dispose 的目的是强制对象执行任何所需的清理,以便可以安全地放弃它。

为了防止代码在没有首先调用 Dispose 的情况下放弃对象,系统允许类注册“终结”。如果某个已注册类的对象被废弃,系统会在该对象被永久废弃之前,给它一个机会对其他实体执行清理操作。但是,无法保证系统会以多快的速度注意到对象已被丢弃,并且各种情况可能会阻止对象被提供清理的机会。术语“托管资源”有时用于指代一个对象,该对象必须在放弃之前执行一些清理,但如果有人未能调用 Dispose,它将自动注册并尝试执行此类清理。

于 2011-02-20T21:30:21.263 回答
0

是的,您有责任调用该Dispose方法 - 或更好的使用using语句。如果一个对象正在实现IDisposable,无论如何你都应该处理它。

using (var myObj = new Whatever())
{
   // ..
}

类似于

{
  var myObj;
  try
  {
     myObj = new Whatever();
     // ..
  } 
  finally
  {
    if (myObj != null)
    {
      ((IDisposable)myObj).Dispose();
    }
  }
} // object scope ends here

编辑:添加了尝试/最后感谢 Talljoe - 哇,要正确处理它很复杂 :)

EDIT2:我并不是说您应该使用第二个变体。我只是想表明“使用”对于一堆可能变得非常混乱且难以正确处理的代码来说是很好的语法糖。

于 2009-06-18T15:21:01.043 回答
0

为什么这对你很重要?

如果可能,我将一次性对象的范围包装在 using 中。这会在使用结束时调用 dispose。当我不再需要该对象时,我会显式调用 dispose 。

无论是原因 1 还是原因 2 都没有必要。

于 2009-06-18T15:24:35.467 回答
-1

这里缺少的一件是终结器 - 我的习惯是,如果我实现 IDisposable 我还有一个终结器来调用 Dispose() 以防万一我的客户没有。是的,它增加了开销,但如果调用 Dispose() ,则调用 GC.SuppressFinalize(this) 会消除它。

于 2009-06-19T13:12:24.603 回答