C#资源释放(托管资源、非托管资源)

本文转载自:https://www.cnblogs.com/ziyeyimeng/articles/2528749.html

它们是托管资源,但管理着非托管资源。怎么理解呢?就好比国家领导:虽然他来自某个省,但他还管着其它省;虽然他是男的,但他还管着女的。

它们所占有的非托管资源由他们自己清理,而不是垃圾回收器清理(垃圾回收器没办法清理非托管资源)。当垃圾回收器回收这些对象,这些对象在被回收时又去清除它们所占有的非托管资源,这就实现了清理所占有的所有资源。

可以看出,虽然这些类管理着非托管资源,但使用起来,似乎可以完全不用考虑这些非托管资源。

作为类使用者,上面的信息就足够了,但作为类设计者,当您设计的类管理着非托管资源时,或者要快速释放资源时,如何达到上面的那些类的效果呢?

类中的 Dispose 方法被设计来释放资源。

首先要明确释放什么?

我们要释放托管资源和非托管资源,托管资源可以被垃圾回收器自动回收,为什么我们还要手动去释放呢?原因是:虽然垃圾回收器可以自动回收,但我们无法确信垃圾回收器什么时候回收,如果所占有的资源相对计算机来说相当精贵,不管它是不是托管的,我们都应该立即释放。

Finalize、Dispose()、Dispose(bool disposing)

Finalize 是 VB 中的内容,C# 中为析构函数;
Dispose() 我们自己定义的释放资源的名称,你甚至可以定义为 Eat() 这些,不过微软推荐用 Dispose()、Close() 这些名字,具体请参见 Connection 对象的 Close 和 Dispose;
Dispose(bool disposing) 是接口 IDisposable 的一个方法,我们在类中实现时方法名和参数都不能变。
Finalize 不能人工调用,它只释放对象中的非托管资源,因为自动调用 Finalize 时,对象中的托管资源或许已经被自动回收。

Dispose() 显示地释放托管资源和非托管资源,如果调用该方法,托管资源和非托管资源将立即得到释放。

根据上述,我们可以发现:Finalize 做的工作是 Dispose() 的一部分,那么为了提高代码的复用,我们是否可以另外写一个方法,该方法可以做全部的清理工作,但也可以根据参数来只做一部分清理工作呢?对了,这就是 IDisposable 的 Dispose(bool disposing) 方法。

public class DisposableResource : IDisposable
{
~DisposableResource()
{
Dispose(false);
}

 public void Dispose()
 {
     Dispose(true);
 }


 //实现接口 IDisposable 的 Dispose(bool disposing) 方法。
 protected void Dispose(bool disposing)
 {
     if (disposing)
     {
         //释放托管资源
     }
    
     //释放非托管资源
 }

}
可以看到,调用 Dispose() 方法后,由于 disposing 为 true,将释放托管和非托管资源,而垃圾回收器调用 Finalize(析构函数)时,将只释放非托管资源(此时托管资源在其它不确信的时候被垃圾回收器回收)。

由于 Dispose() 已经释放了所有资源,垃圾回收器还有没有必要再调用 Finalize 呢?没有必要了。所以我们要告诉垃圾回收器不需要再调用 Finalize 了,故在 Dispose() 中增加一句,代码如下:

public class DisposableResource : IDisposable
{
~DisposableResource()
{
Dispose(false);
}

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


 //实现接口 IDisposable 的 Dispose(bool disposing) 方法。
 protected void Dispose(bool disposing)
 {
     if (disposing)
     {
         //释放托管资源
     }
    
     //释放非托管资源
 }

}
还有一个问题,如何让程序更健壮些,以避免多次调用 Dispose 时出错,我们可以增加一个字段来表明是否已经清理过资源了,若已经清理了,则不再清理,代码如下:

public class DisposableResource : IDisposable
{
private bool _disposed = false;

 ~DisposableResource()
 {
     Dispose(false);
 }


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


 //实现接口 IDisposable 的 Dispose(bool disposing) 方法。
 protected void Dispose(bool disposing)
 {
      if (!_disposed)
     {
         if (disposing)
         {
             //释放托管资源
         }
        
         //释放非托管资源
        
         _disposed = true;

    }
 }

}

实际上,上述代码,就是一个标准的 Dispose 设计模板。

如果我们有基类,我们还需要在 Dispose() 中调用基类的 Dispose() 方法。
如果我们的类中有一个字段为 Image _img,应该在 disposing 为 true 的情况下调用 _img.Dispose(),因为 Image 是个托管资源类型,虽然它管理着非托管资源。
我们在使用类时:

如果调用了 Dispose(),则托管资源和非托管资源都将被释放,事情也就此完成。
如果没有调用 Dispose(),在某个时候(无法确信具体时间),垃圾回收器将调用 Finalize,Finalize 将释放非托管资源;而另一方面,也在某个时候,垃圾回收器将回收该对象的托管资源。
所以,如果类提供了 Dispose(),我们还是应该调用它:

一方面让内存早点得到释放。
另一方面避免 Finalize 被调用,因为调用 Finalize 开销要大些。
Finalize 是 Visual Basic 中的方法,在 C# 中没有这个关键词,而是使用析构函数,为了和网上的介绍一致,本文也使用 Finalize 进行介绍,C# 用户可以直接把“Finalize”换成“析构函数”进行理解。

两个概念

为了便于理解,提出两个概念。

类设计者:编写类的人。
类使用者:应用类实例的人。
Finalize 和 Dispose

Finalize 和 Dispose 都是释放资源,Finalize 隐式释放资源,Dispose 显式释放资源,怎么理解呢?Finalize 是对象不可访问后自动被调用的,Dispose 是类使用者调用的。

对于类设计者:

Finalize 和 Dispose 释放的资源应该相同(除托管资源外,原因见本连载。),这样即使类使用者在没有调用 Dispose 的情况下,资源也会在 Finalize 中得到释放。
Dispose 中应该调用 GC.SuppressFinalize 方法,这样类使用者调用了 Dispose 后,就不会自动调用 Finalize 了,因为调用 Dispose 后没有必要再执行 Finalize。
Finalize 不应为 public。
对于类使用者:

有 Dispose 方法存在时,应该调用它,因为 Finalize 释放资源通常是很慢的。
也就是说,对于类使用者,我们只需要调用 Dispose 就可以了,不需要关注 Finalize,因为 Finalize 通常不是 public 的。如果类使用者没有调用 Dispose 方法,Finalize 是释放资源的最后防线。当然这些都是建立在类设计者遵照上述规则设计的前提下。

顺便说一下,有文提到 Dispose(bool disposing),这个带参数的 Dispose 通常是类设计者的事情,类使用者通常不会遇到。

那么 Close 是什么呢?

Close 这个方法在不同的类中有不同的含义,并没有任何规定要求 Close 具有特殊的含义,也就是说 Close 并不一定要释放资源,您也可以让 Close 方法表示“关门”。

不过,由于 Close 有“关”的意思,通常也把 Close 拿来释放资源,这也是允许的。比如文件操作中,用 Close 释放对象似乎比 Dispose 含义更准确,于是在设计类时,可以将 Close 设为 public,将 Dispose 设为 protected,然后由 Close 调用 Dispose。

也就是说 Close 表示什么意思,它会不会释放资源,完全由类设计者决定。网上说“Close 调用 Dispose”这种方法是很片面的。在 SqlConnection 中 Close 只是表示关闭数据库连接,并没有释放 SqlConnection 这个对象资源。

根据经验,Close 和 Dispose 同时存在的情况下(均为 public),Close 并不表示释放资源,因为通常情况下,类设计者不应该使用两个 public 方法来释放相同的资源。