“Dispose”只应用于包含非托管资源的类型吗?

时间:2012-04-25 13:12:53

标签: c# garbage-collection idisposable finalizer finalization

我最近与同事讨论了Dispose的价值以及实施IDisposable的类型。

我认为实施IDisposable对于应该尽快清理的类型是有价值的,即使没有非托管资源可以清理

我的同事有不同的想法;如果您没有任何非托管资源,则不需要实现IDisposable,因为您的类型最终将被垃圾收集。

我的论点是,如果您想要尽快关闭ADO.NET连接,那么实施IDisposableusing new MyThingWithAConnection()是有意义的。我的同事回答说,在幕后,ADO.NET连接是非托管资源。我对他回复的回复是一切最终都是一种非托管资源

我知道recommended disposable pattern如果Dispose被调用,免费提供托管和非托管资源,但只有免费的非托管资源(如果通过终结器/析构函数调用) (前一段时间关于如何alert consumers of improper use of your IDisposable types

的博客

所以,我的问题是,如果你有一个不包含非托管资源的类型,是否值得实现IDisposable

15 个答案:

答案 0 :(得分:35)

IDisposable有不同的有效用途。一个简单的例子就是保存一个打开的文件,只要你不再需要它就需要在某个时刻关闭。当然,您可以提供方法Close,但在Dispose中使用该方法并使用using (var f = new MyFile(path)) { /*process it*/ }这样的模式会更加例外。

一个更受欢迎的示例是保留其他一些IDisposable资源,这通常意味着您需要提供自己的Dispose以便处理它们。

一般情况下,只要您想要确定性地销毁任何内容,就需要实施IDisposable

我和你的意见之间的区别在于,一旦某些资源需要确定性销毁/释放,我就会实施IDisposable,而不是必须尽快 。在这种情况下,依赖垃圾收集不是一种选择(与你同事的说法相反),因为它发生在不可预测的时刻,实际上可能根本不会发生!

任何资源在封面下都不受管理这一事实并不意味着什么:开发人员应该考虑“何时以及如何处理此对象”,而不是“它如何在封面下工作” ”。无论如何,底层实现可能随时间而变化。

事实上,C#和C ++之间的主要区别之一是缺少默认的确定性破坏。 IDisposable缩小了差距:您可以订购确定性销毁(虽然您无法确保客户端正在调用它;但在C ++中,您无法确定客户端是否在对象上调用delete )。


小补充:确定性释放资源并尽快释放之间的差异实际上有什么区别?实际上,那些是不同的(虽然不是完全正交的)概念。

如果要确定性地释放资源,这意味着客户端代码应该可以说“现在,我希望释放此资源”。这可能实际上不是可以释放资源的最早可能的时刻:持有资源的对象可能已从资源获得所需的一切,因此可能已经释放资源。另一方面,即使在对象的Dispose经过之后,对象也可能选择保留(通常是非托管的)资源,仅在终结器中清理它(如果保留资源太长时间没有任何问题) )。

因此,为了尽快释放资源 ,严格来说,Dispose不是必需的:对象可以在它意识到资源不是时立即释放资源需要更多。 Dispose但是作为一个有用的提示,不再需要对象本身,因此,如果适当的话,也可以在那时释放资源


还有一个必要的补充:不仅需要确定性解除分配的非托管资源!这似乎是这个问题答案中意见分歧的关键点之一。人们可以拥有纯粹富有想象力的结构,可能需要确定性地释放。

示例包括:访问某些共享结构的权利(想想RW-lock),一个巨大的内存块(想象一下你手动管理一些程序的内存),使用其他程序的许可证(想象一下)你不能同时运行某些程序的 X 副本),等等。这里要释放的对象不是非托管资源,而是来做/使用某些东西,这是程序逻辑的纯粹内部构造。


小补充:这里有一小部分[ab]使用IDisposablehttp://www.introtorx.com/Content/v1.0.10621.0/03_LifetimeManagement.html#IDisposable的简洁例子。

答案 1 :(得分:17)

我认为在责任方面考虑IDisposable是最有帮助的。一个对象应该实现IDisposable,如果它知道在不再需要它和宇宙结束之间需要完成的事情(并且最好是尽快),并且如果它是两者的唯一对象这样做的信息和动力。例如,打开文件的对象将有责任查看文件是否已关闭。如果对象只是在不关闭文件的情况下消失,则文件可能无法在任何合理的时间范围内关闭。

重要的是要注意,即使只与100%托管对象交互的对象也可以执行需要清理的事情(并且应该使用IDisposable)。例如,附加到集合的“已修改”事件的IEnumerator将需要在不再需要时自行分离。否则,除非枚举器使用一些复杂的技巧,否则只要集合在范围内,枚举器就永远不会被垃圾收集。如果集合被枚举了一百万次,那么一百万个枚举器将附加到它的事件处理程序。

请注意,有时可以使用终结器进行清理,无论出于何种原因,在没有首先调用Dispose的情况下放弃对象。有时候效果很好;有些东西很糟糕。例如,即使Microsoft.VisualBasic.Collection使用终结器将枚举器从“已修改”事件中分离出来,尝试枚举此类对象数千次而没有介入Dispose或垃圾回收将导致它变得非常慢 - 比正确使用Dispose时所产生的性能慢几个数量级。

答案 2 :(得分:9)

  

所以,我的问题是,如果你有一个不包含的类型   非托管资源,是否值得实施IDisposable?

当有人在对象上放置IDisposable接口时,这告诉我创建者打算在该方法中执行某些操作,或者在将来他们可能打算这样做。我总是在这个例子中称为dispose只是为了确定。即使它现在没有做任何事情,它可能在未来,并且因为更新了对象而导致内存泄漏很糟糕,并且在第一次编写代码时没有调用Dispose。

事实上,这是一个判断。你不想过度实现它,因为在那一点上为什么还要烦扰垃圾收集器。为什么不手动处理每个对象。如果您有可能需要处置非托管资源,那么这可能不是一个坏主意。这完全取决于,如果使用您的对象的唯一人员是您团队中的人员,您可以随后跟进他们并说:“嘿,现在需要使用非托管资源。我们必须仔细检查代码并确保我们整理了一下。“如果您要将其发布给其他组织以使用其他组织。没有简单的方法可以告诉每个可能已经实现该对象的人,“嘿,你需要确保现在已经处理掉了。”让我告诉你,有些事情让人们感到羞耻,而不是升级第三方程序集,以发现他们是那些更改代码并使你的应用程序逃避内存问题的人。

  

我的同事回答说,在封面下,ADO.NET连接是一个   管理资源。我对他的答复的答复是最终的一切   是一种非托管资源。

他是对的,现在它是一个托管资源。他们会改变吗?谁知道,但称之为没有伤害。我没有尝试猜测ADO.NET团队做了什么,所以如果他们把它放入并且什么都不做,那就没问题。我仍然会称之为,因为一行代码不会影响我的工作效率。

您还遇到了另一种情况。假设您从方法返回ADO.NET连接。您不知道ADO连接是基础对象还是派生类型。您不知道是否突然需要该IDisposable实现。无论如何,我总是称之为,因为生产服务器上的内存泄漏在每4小时崩溃时就会很糟糕。

答案 3 :(得分:6)

虽然已经有了很好的答案,但我只是想做一些明确的事情。

实施IDisposable有三种情况:

  1. 您正在直接使用非托管资源。这通常涉及从P / Invoke调用中检索IntPrt或其他形式的句柄,该调用必须由不同的P / Invoke调用释放
  2. 您正在使用其他IDisposable个对象,需要对其处置负责
  3. 您还有其他需要或使用它,包括using块的便利性。
  4. 虽然我可能有点偏颇,但您应该阅读(并向同事展示)the StackOverflow Wiki on IDisposable

答案 4 :(得分:5)

不,对于非托管资源,

它建议像框架调用的基本清理内置机制,它使您可以清理所需的任何资源,但最适合的是自然的非托管资源管理。

答案 5 :(得分:5)

请注意,非托管资源可能包含标准CLR对象,例如保存在某些静态字段中,所有对象都以安全模式运行,根本没有非托管导入。

没有简单的方法可以判断实现IDiposable的给定类是否确实需要清理某些内容。我的经验法则是总是在我不太了解的对象上调用Dispose,就像第三方库一样。

答案 6 :(得分:5)

Dispose应该用于有限生命期的任何资源。终结器应该用于任何非托管资源。任何非托管资源都应该具有有限的生命周期,但是有大量的托管资源(如锁)也具有有限的生命周期。

答案 7 :(得分:3)

如果你聚合IDisposable,那么你应该实现界面,以便及时清理这些成员。在你引用的ADO.Net连接示例中,如何调用myConn.Dispose()

我认为在这种情况下,所有内容都是非托管资源是不正确的。我也不同意你的同事。

答案 8 :(得分:3)

你是对的。托管数据库连接,文件,注册表项,套接字等都保留在非托管对象上。这就是他们实施IDisposable的原因。如果您的类型拥有一次性对象,则应实现IDisposable并将其置于Dispose方法中。否则,他们可能会一直活着,直到垃圾收集,导致锁定文件和其他意外行为。

答案 9 :(得分:3)

  

一切最终都是一种非托管资源。

不正确。 CLR对象使用的内存以外的所有内容,只由框架管理(分配和释放)。

在未保留任何非托管资源(直接或间接通过相关对象)的对象上实施IDisposable并调用Dispose 毫无意义。它确实释放该对象确定性,因为无法直接释放对象的CLR内存,因为它总是只有{{1这样做。对象是引用类型,因为值类型在直接在方法级别使用时,由堆栈操作分配/释放。

现在,每个人都声称他们的答案是正确的。让我证明我的。根据{{​​3}}:

  

Object.Finalize Method允许对象在垃圾收集回收之前尝试释放资源并执行其他清理操作

换句话说,在调用GC之后释放对象的CLR内存。 [注意:如果需要,可以明确地跳过此调用]

这是一个没有非托管资源的一次性课程:

Object.Finalize()

请注意,documentation隐式调用继承链中的每个internal class Class1 : IDisposable { public Class1() { Console.WriteLine("Construct"); } public void Dispose() { Console.WriteLine("Dispose"); } ~Class1() { Console.WriteLine("Destruct"); } } Finalize

以下是控制台应用的Object.Finalize()方法:

Main

如果调用static void Main(string[] args) { for (int i = 0; i < 10; i++) { Class1 obj = new Class1(); obj.Dispose(); } Console.ReadKey(); } 是一种以确定方式释放托管对象的方法,那么每个“Dispose”会立即跟随“Destruct”,对吗?看看自己会发生什么。从命令行窗口运行此应用程序是最有趣的。

注意:有一种方法可以强制Dispose收集当前应用程序域中待定位的所有对象,但对于单个特定对象则不会。不过,您无需调用GC在终结队列中拥有对象。强烈建议不要强行收集,因为这可能会影响整体应用程序的性能。

修改

有一个例外 - 国家管理。如果您的对象恰好管理外部状态,Dispose可以处理状态更改。即使状态不是非托管对象,由于Dispose的特殊处理,使用它也非常方便。示例可以是安全上下文或模拟上下文。

IDisposable

这不是最好的例子,因为using (WindowsImpersonationContext context = SomeUserIdentity.Impersonate())) { // do something as SomeUser } // back to your user 在内部使用了系统句柄,但你得到了图片。

底线是,在实施WindowsImpersonationContext时,您需要(或计划拥有)IDisposable方法中有意义的事情。否则,这只是浪费时间。 Dispose不会更改GC管理对象的方式。

答案 10 :(得分:1)

如果它引用了非托管资源,或者它包含对实现IDisposable的对象的引用,那么你的Type应该实现IDisposable。

答案 11 :(得分:1)

在我的一个项目中,我有一个带有托管线程的类,我们将它们称为线程A,线程B和IDisposable对象,我们将其称为C.

用于处理退出时的C. B曾经使用C来保存例外。

我的班级必须实施IDisposable和descrtuctor,以确保以正确的顺序处理事情。 是的,GC可以清理我的物品,但我的经验是有一个竞争条件,除非我管理我的班级清理。

答案 12 :(得分:1)

简答:绝对不是。如果您的类型具有托管或非托管成员,则应实现IDisposable。

现在详情: 我已回答了这个问题,并在StackOverflow上提供了有关内存管理内容和GC问题的更多细节。这里只是几个:

关于IDisposable实施的最佳实践,请参阅我的博客文章:

<强> How do you properly implement the IDisposable pattern?

答案 13 :(得分:0)

如果对象拥有任何非托管对象任何托管的一次性对象

,请实施IDisposable

如果对象使用非托管资源,则应实现IDisposable。拥有一次性对象的对象应实现IDisposable以确保释放基础非托管资源。如果遵循规则/约定,那么可以合理地得出结论:不处理托管的一次性对象等于不释放非托管资源。

答案 14 :(得分:0)

根本不需要资源(托管或非托管)。通常,IDisposable只是方便的方式来消除精简版try {..} finally {..},只需比较:

  Cursor savedCursor = Cursor.Current;

  try {
    Cursor.Current = Cursors.WaitCursor;

    SomeLongOperation();
  }
  finally {
    Cursor.Current = savedCursor;
  }

  using (new WaitCursor()) {
    SomeLongOperation();
  }

其中WaitCursor IDisposable适合using

  public sealed class WaitCursor: IDisposable {
    private Cursor m_Saved;

    public Boolean Disposed {
      get;
      private set;
    }

    public WaitCursor() {
      Cursor m_Saved = Cursor.Current;
      Cursor.Current = Cursors.WaitCursor;
    }

    public void Dispose() {
      if (!Disposed) {
        Disposed = true;
        Cursor.Current = m_Saved;
      }
    }
  }

您可以轻松合并这些类:

  using (new WaitCursor()) {
    using (new RegisterServerLongOperation("My Long DB Operation")) {
      SomeLongRdbmsOperation();  
    }

    SomeLongOperation();
  }