【问题标题】:Please, some clarifications on C# IDisposable请对 C# IDisposable 做一些澄清
【发布时间】:2011-11-03 11:29:41
【问题描述】:

我在不同的线程和不同的论坛中多次看到下面的代码。这个特别是我从How does GC and IDispose work in C#?收到的。

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'有什么意义?为什么有必要区分处置托管对象和非托管对象?

【问题讨论】:

标签: c#


【解决方案1】:

垃圾收集器本身对IDisposable 一无所知。它不会为你处理任何东西——它只会调用终结器。

使用“disposing”参数进行重载的要点是终结器将调用 Dispose(false) 以表明它是从终结器调用的,并且托管对象不需要任何清理,而如果你调用 @ 987654326@ 显式(例如通过using 语句)最终将调用Dispose(true)。

这种模式的部分要点在于它可以为派生类扩展 - 只有基类终结器需要调用 Dispose,如果需要,其他所有内容都将通过覆盖 Dispose(bool) 来搭载它。

但是,除非您实际上可以直接访问非托管资源 - 或期望派生类 - 您可能根本不需要终结器。如果您确实需要相当直接的访问,那么SafeHandle 有助于避免编写终结器的需要。你应该almost never need to write a finalizer these days。就我个人而言,我自己很少实现IDisposable,当我这样做时,它通常来自一个密封类(因为我喜欢尽可能密封类) - 它几乎从不涉及终结器......所以我只是编写一个Dispose 方法来实现接口并保留它。简单得多。

full advice for implementing IDisposable in every situation you can imagine 非常冗长和复杂。我认为尽可能将自己限制在更简单的情况下是值得的。

【讨论】:

  • IDisposable 非常适合清空事件。即使我没有终结器,我仍然会创建 Dispose(bool) 以便继承者可以正确调用它(我想这在密封类的情况下是没有实际意义的)。此外,您的链接不是线程安全的,_isDisposed 应该是 int,并且应该通过 if (Interlocked.Exchange(ref _isDisposed, 1) == 0) { /* Object was not disposed before method was invoked */ } 进行检查/更新
  • @Jonathan:我最近没有通读它,但我怀疑它在某处谈到了线程。它是由了解这类事情的人编写的 - 它至少在一个示例中明确提到:“注意:以下代码不处理线程安全问题,它仅用于传达概念。”
  • 啊,我只是略读了一下。非常有用的资源(作为最后的 IDisposables 线程的旁注总是一个问题,即使您的代码不是多线程的)。
【解决方案2】:

1 是否有必要创建显式析构函数?

仅在您直接拥有非托管资源的极少数情况下。

该类继承自 IDisposable,并且在 GC 清理期间最终会执行 Dispose()。

IDisposable 接口只允许在using(){}blocks 中使用。 GC 最终将调用 Dispose() 但这将(为时已晚)。请注意,它将使用disposing==false 并且只尝试清理非托管的东西。你很可能没有。

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

因为在 Disposing 时不需要 Dispose() 托管资源。 GC 的算法可确保您的托管资源本身已经被 GC'ed。调用他们的 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() { }

}

【讨论】:

    【解决方案3】:

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

    2:它告诉Dispose(bool) 代码它是否在垃圾回收中(如果是false)。在被收集时,您不应该触摸您自己之外的任何其他对象,因为它们可能已经消失了。但是,如果您被选择性地处置(即true),您可能需要清理一些封装 托管对象;例如,在要包装的连接上调用 .Close()

    【讨论】:

      【解决方案4】:
      1. 不,您不需要实现析构函数。实际上这是微软自己强烈不推荐的。
      2. disposing 用于识别 Dispose 方法的确切调用者。

      【讨论】:

      • s/destructor/finalizer/。后者与确定性清理有关,而这正是您无法得到的。
      • @delnan:Microsoft 的 C# 规范将其称为析构函数。 ECMA 规范将其称为终结器。这是一个不幸的命名选择,但称它为析构函数仍然是正确的。
      • @delna 但是析构函数是 C# 中的官方术语。之前已经指出,这可能是一个糟糕的选择。
      • @Jon Skeet:是的,我知道一些官方资料称它为析构函数。但正如你所说,这是一个不幸的名字,所以我强烈建议停止使用它。
      • @delnan 比 ECMA 规范更清楚 :) 我的意思是带有 ~ 的方法是析构函数
      【解决方案5】:

      正如您提到的,析构函数是在 GC 清理期间执行 Dispose 的原因。所以你需要它来确保如果程序员之前没有明确地释放对象,对象最终会被释放。

      IDisposable 存在的真正原因是将非托管 资源返回给系统。这就是为什么你有“disposed unmanaged stuff always”注释的原因:非托管资源应该在程序员显式调用Dispose和终结器执行时都被释放(但是,请注意,无参数Dispose方法将显式阻止终结器被执行——这可以防止资源的双重处理,也有利于性能)。

      disposing 参数用于区分显式 Dispose(由程序员)和在终结器内触发的隐式资源处置。如果您的类有 IDisposable 类型的成员,那么处置您的对象很可能也处置那些其他成员对象,因此代码也运行“也处置托管的东西”分支。另一方面,如果你没有显式地处理对象(它是由 GC 运行的终结器),那么这些其他对象可能已经被垃圾回收了。如果是这种情况,您不会想触摸它们。

      【讨论】:

      • 重新术语:使用终结器和析构器;例如,这是 2010 年 MSDN 参考:msdn.microsoft.com/en-us/library/66x5fx1b.aspx
      • 而IDisposable 是关于及时进行 - 强调when;它并不直接与非托管相关,因为终结器/析构函数可以做到这一点。有完全托管的 IDisposable 实现的示例
      • @MarcGravell:确实如此。试图在这里用几段写一篇文章:)
      • 不,在 C# 中它被称为析构函数。
      • @HenkHolterman:你确实有令人信服的论点,经过编辑以反映它。恕我直言,这整个终结器/析构器术语本身就太复杂了。
      猜你喜欢
      • 2013-08-06
      • 1970-01-01
      • 2014-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多