调用 Dispose 到底做了什么?

What exactly does calling Dispose do?

我一直在努力加深对垃圾收集、托管和非托管资源以及 "proper design principles" 内存管理的理解,因为我有兴趣进入 "lower level" 编程和类似的领域.

我知道您应该使用using块或其他一些解决方案来确保这些非托管资源确实得到处置,但我不了解幕后发生的事情。

我在 MSDN 上查看这篇文章:Implementing a Dispose Method 并被这一行弄糊涂了:

To help ensure that resources are always cleaned up appropriately, a Dispose method should be callable multiple times without throwing an exception.

让我们看看他们为处置模式提供的示例代码:

class DerivedClass : BaseClass
{
   // Flag: Has Dispose already been called?
   bool disposed = false;

   // Instantiate a SafeHandle instance.
   SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true);

   // Protected implementation of Dispose pattern.
   protected override void Dispose(bool disposing)
   {
      if (disposed)
         return; 

      if (disposing) 
      {
         handle.Dispose();
         // Free any other managed objects here.
      }

      // Free any unmanaged objects here.

      disposed = true;

      // Call base class implementation.
      base.Dispose(disposing);
   }
}

相信上面引用的文字基本上是说,"We added a bool disposed property so that we can check if that's true and return if so. If it's false then we dispose of the actual resources. That way we don't actually dispose of something multiple times"

但这对我来说没有意义。如果我们已经摆脱了一个对象,你怎么能对它调用 Dispose 第二次?

为了调查,我编写了一个包含以下 3 行的控制台应用程序:

var cmd = new SqlCommand();
cmd.Dispose();
cmd.Dispose();

这可以毫无问题地编译和执行——考虑到文章中引用的文本,这是有道理的。但我不明白 实际上 发生了什么。

我设置了一个断点并跨过了每一行。在调用第一个 Dispose 之后,我希望 Visual Studio 中的本地人 window 告诉我 cmdnull。按照这个思路,我问自己,"How can you call Dispose on null?" 显然,你不能。那么发生了什么?为什么 cmd 在第一次处理后仍然是 SqlCommand 对象?

Dispose 做了什么,如果我已经处理了我的对象,为什么它似乎仍然存在于所有意图和目的?

为什么/如何编译以下内容并且 运行 没有问题?

var cmd = new SqlCommand();
cmd.Dispose();
cmd.CommandText = "I figured this would throw an exception but it doesn't";

除了 using 语句可以为您调用之外,Dispose 没有什么特别之处。除此之外,它就像任何其他 .NET 函数一样。它不会神奇地将任何变量设置为 null,导致在再次使用该对象或类似情况时抛出异常。

同样,IDisposable 在任何方面都不是特殊接口,只是 using 语句中的对象必须实现它。您可以将 using 语句看作是这样的:

// using(var a = somethingDisposable()) {otherCode();}
var a = somethingDisposable();
try
{
    otherCode();
}
finally
{
    if(a != null)
        ((IDisposable)a).Dispose();
}

IDisposable 接口被多个消费 classes 调用,并且 - 正如 Dark Falcon 已经指出的那样 - 当您将 class 的用法封装在一个 using 块中时。这样可以更轻松地保持非托管资源的清洁。 但是 Dispose 就像任何其他方法一样,不要与 destructors/finalizers 混淆(这显然是您所期望的)