请提供有关C# IDisposable的一些澄清说明

9

我在不同的帖子和论坛中多次看到了以下代码。这个特别的是我从如何在C#中使用GC和IDispose?中学到的。

class MyClass : IDisposable
{
    ...

    ~MyClass()
    { 
        this.Dispose(false);
    }

    public void Dispose()
    {
        this.Dispose(true);
       GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        { /* dispose managed stuff also */ }

        /* but dispose unmanaged stuff always */
    }
}

我的问题是:

  1. 是否有必要创建显式析构函数?该类继承自IDisposable接口,在GC清理期间,Dispose()方法最终会被执行。

  2. Dispose(bool disposing)方法中的参数'disposing'的意义是什么?为什么需要区分托管对象和非托管对象的处理?


1
IDisposable 的权威资源是 Joe Duffy 的这篇文章:http://www.bluebytesoftware.com/blog/2005/04/08/DGUpdateDisposeFinalizationAndResourceManagement.aspx。 - Jeremy McGee
这个问题中的答案有帮助吗:https://dev59.com/qHNA5IYBdhLWcg3wmfEa? - Adam Houldsworth
很多很棒的答案和链接。真希望我能把它们都选为最佳答案。谢谢大家分享! - Ronald
5个回答

5
垃圾回收器本身不知道任何关于IDisposable的信息,它不会为您处理任何清理工作 - 它所做的只是调用终结器。使用“disposing”参数的重载的目的在于,终结器将调用Dispose(false)以指示已从终结器中调用并且托管对象不需要进行任何清理,而如果您显式调用Dispose(例如通过using语句),这将最终调用Dispose(true)。此模式的部分要点是它可扩展到派生类 - 只有基类终结器需要调用Dispose,如果必要,其他所有内容都可以通过覆盖Dispose(bool)来利用该类。
然而,除非你确实直接访问未管理的资源 - 或者期望派生类这样做 - 否则你可能根本不需要 finalizer。如果你确实需要相当直接的访问,则 SafeHandle 有助于避免编写 finalizer 的需要。现在几乎 从不需要编写 finalizer。个人而言,我很少自己实现 IDisposable,而且当我这样做时,通常是从封闭类(因为我喜欢尽可能封闭类)开始 - 它几乎永远不涉及 finalizer...所以我只编写一个单独的 Dispose 方法来实现接口并将其留在那里。更简单。

实现IDisposable的完整建议在各种情况下都非常冗长和复杂。我认为尽可能限制自己只在简单情况下使用它是值得的。


IDisposable非常适用于将事件置空。即使我没有终结器,我仍然创建了Dispose(bool),以便继承者可以正确地调用它(在密封类的情况下,这可能是无意义的)。此外,您的链接不是线程安全的,_isDisposed应该是一个int,并且应该通过if (Interlocked.Exchange(ref _isDisposed, 1) == 0) { /* Object was not disposed before method was invoked */ }进行检查/更新。 - Jonathan Dickinson
@Jonathan:最近我没有完整地阅读过它,但我怀疑它在某个地方讨论了线程。它是由懂得这种事情的人编写的,并且至少在一个示例中明确提到:“注意:以下代码不处理线程安全问题,仅旨在传达概念。” - Jon Skeet
啊,我只是粗略地浏览了一下。尽管如此,它仍然是一个非常有用的资源(顺便提一句,在最终确定的IDisposable线程中,即使您的代码不是多线程的,线程问题始终存在)。 - Jonathan Dickinson

3

1 需要创建显式解构函数吗?

在您直接拥有非托管资源的罕见情况下需要。

该类继承自IDisposable,Dispose()将在GC清除期间被最终执行。

IDisposable接口仅允许在使用using(){}块时使用。 GC最终将调用Dispose(),但那将会(太)晚。请注意,它将使用disposing==false并尝试清理非托管的东西。 您很可能没有这些。

2 Dispose(bool disposing)中的参数'disposing'的重要性是什么?为什么需要区分处理托管和非托管对象?

因为在处置时没有必要Dispose()托管资源。 GC算法确保您的托管资源已经被GC处理。调用它们的Dispose()最多是无害的。

请注意,此代码基于标准实现模式。如果您不含解构函数,则过载的Dispose(bool)唯一作用是继承。

一个较短的版本,请注意sealed

sealed class MyClass : IDisposable
{
   private FileStream MyManagedResource;

   public void Dispose()
   {
       //this.Dispose(true);
       //GC.SuppressFinalize(this);

       /* dispose managed stuff  */
       if (MyManagedResource != null)
          MyManagedResource.Dispose();  // this is why we do it all
   }

   // ~MyClass() { }

}

2

1: 不需要,这只在直接包装外部非托管资源(如Windows句柄)的类中很常见;或者如果可能有一些子类会这样做。如果您只处理托管对象,则添加终结器实际上是一件坏事情,因为它会影响WAT收集工作。

2: 它告诉Dispose(bool)代码当前是否处于垃圾回收状态(如果为false)。在被收集时,您不应该触及任何自己以外的其他对象,因为它们可能已经不存在了。但是,如果您正在进行有选择性的处理(即true),则可能希望清理一些封装的托管对象;例如,在包装连接的情况下调用.Close()


1
  1. 不需要实现析构函数。事实上,这是由 Microsoft 强烈不推荐的。
  2. disposing 用于标识 Dispose 方法的确切调用者。

s/destructor/finalizer/。后者与确定性清理相关,而这正是您所没有的。 - user395760
@delnan:微软的C#规范称其为析构函数。ECMA规范称其为终结器。这是一个不幸的命名选择,但仍然正确将其称为析构函数。 - Jon Skeet
@delna 但是在C#中,析构函数是官方术语。以前已经注意到这可能是一个糟糕的选择。 - H H
@Jon Skeet:是的,它并不是“错误”的,也许我的原始评论在这一点上应该更清晰(可惜现在已经无法编辑了)。 - user395760
@Tigran:如果您直接访问非托管资源,建议实现一个finalizer/destructor,但这种情况极为罕见,自从引入SafeHandle后更加罕见。我会找到相关的Joe Duffy链接并将其编辑到我的答案中... - Jon Skeet
显示剩余4条评论

0
析构函数是在垃圾回收清理期间执行Dispose的原因,正如您所提到的那样。因此,如果程序员没有显式地处理对象,您需要它来确保对象最终被处理。 IDisposable存在的原因就是为了将非托管资源返回给系统。这就是您有“始终处理非托管资源”的注释的原因:当程序员显式调用Dispose和执行终结器时,应该处理非托管资源(但请注意,无参数的Dispose方法将明确防止终结器被执行——这可以防止资源被重复处理,并且对性能也有好处)。

disposing参数用于区分程序员显式调用Dispose和在终结器内部触发的隐式资源释放。如果您的类具有IDisposable类型的成员,则很可能应该处理对象的释放,以便处理这些其他成员对象,因此代码还会运行“dispose managed stuff also”分支。另一方面,如果您没有显式处理对象的释放(而是由GC运行的终结器来完成),则这些其他对象可能已经被垃圾回收。如果是这种情况,您不希望触及它们。


关于术语:使用了finalizer和destructor;例如,这是2010 MSDN参考文献:http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx - Marc Gravell
IDisposable 是关于及时执行的 - 重点在于 何时; 它并不直接涉及非托管资源,因为 finalizer/析构函数可以处理。有一些完全托管的 IDisposable 实现的例子。 - Marc Gravell
@MarcGravell:确实。在这里试图用几个段落来适应一篇论文 :) - Jon
不,用C#时它被称为析构函数。 - H H
@HenkHolterman: 你提出的论点确实很有说服力,经过修改后反映出来了。在我看来,整个finalizer/destructor术语过于复杂,没有必要这么细致。 - Jon
@Jon:请查看http://stackoverflow.com/q/5882729/60761,并注意链接到Eric Lipperts博客的链接。 - H H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接