为什么要采用复杂的方案来正确实现 IDisposable?

Sar*_*ien 3 c# idisposable finalizer

今天早些时候,我在工作中对某些代码运行代码分析时遇到了CA1063 。

我有两个问题:

  1. 为什么以下代码不会导致 CA1063,即使它明显违反了某些要求(例如 Dispose 被覆盖)

  2. 代码的实际问题是什么,导致了由密封的 Dispose() 和终结器等调用的虚拟 Dispose(bool) 的复杂方案。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
  class Foobar : IDisposable
  {
    public Foobar()
    {
      Console.Out.WriteLine("Constructor of Foobar");
    }

    public virtual void Dispose()
    {
      Console.Out.WriteLine("Dispose of Foobar");
      GC.SuppressFinalize(this);
    }

    ~Foobar()
    {
      Console.Out.WriteLine("Finalizer of Foobar");
    }
  }

  class Derived : Foobar
  {
    public Derived()
    {
      Console.Out.WriteLine("Constructor of Derived");
    }

    public override void Dispose()
    {
      Console.Out.WriteLine("Dispose of Derived");
      GC.SuppressFinalize(this);
      base.Dispose();
    }

    ~Derived()
    {
      Console.Out.WriteLine("Finalizer of Derived");
    }
  }

  class Program
  {
    static void Main()
    {
      Console.Out.WriteLine("Start");
      using (var foo = new Derived())
      {
        Console.Out.WriteLine("...");
      }
      Console.Out.WriteLine("End");
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

sup*_*cat 5

最初,微软期望许多类型的对象能够封装托管和非托管资源,并且即使特定的可继承类没有封装任何非托管资源,从它派生的类也可能会封装任何非托管资源。尽管这种想法在很大程度上是错误的(通常最好将非托管资源隔离到它们自己的对象中,然后可以将其用作托管资源),但旨在处理任意混合的托管和非托管资源的模式已成为既定的先例。

尽管完整 Dispose 模式的某些部分很愚蠢,但适当的简化不会遗漏很多内容。清理代码应该位于受保护的虚拟方法中,以便允许派生类添加自己的逻辑,但仍然链接到父类方法;如果该方法被赋予名称Dispose,则它必须具有与无参数方法不同的签名Dispose[尽管我自己的偏好是具有不同名称的无参数方法]。我对微软模式最大的抱怨是它要求每个派生类都有自己的逻辑来防止重复处置;让基类在非虚拟实现中处理这个问题会更干净Dispose。