目的

为了及时释放宝贵的非托管资源和托管资源,并且保证资源在被 gc 回收的时候可以正确释放资源,同时兼顾执行效率。

必须遵循的事实

  • 托管资源释放:

    • 由另一线程的 gc 进行释放,当托管的对象没有被引用时,就会在“适当的时候”进行回收。
    • 如果定义了析构函数,回收的时候会调用析构函数(实际执行可能有差别),之后释放对象占用的内存。
    • 当类有析构函数时, gc 会分分两步来释放,如果没有析构函数或者指定不需要调用析构函数时,只需要一步就能释放。
  • 非托管资源必须显式释放

方案

把资源释放都放在析构函数里

可以保证资源都释放,但是由于 gc 调用时机的不确定性,导致宝贵的非托管资源无法及时释放。

写个释放函数,手动是调用

如果忘了释放的话, 托管资源会被 gc 释放,但非托管资源就无法释放

Dispose 模式。参考下面的代码

手动调用Dispose() 可以释放所有资源,并且在 gc 标记不需要再调用析构函数,从而提高了效率。如果忘记调用Dispose(), 则当 gc 调用析构函数的时候也会把非托管资源释放掉。

  1. public interface IDisposable
  2. {
  3. void Dispose();
  4. }
  5. public class DisposablClass : IDisposable
  6. {
  7. //是否回收完毕
  8. bool _disposed;
  9. public void Dispose()
  10. {
  11. Dispose(true);
  12. GC.SuppressFinalize(this); //标记gc不在调用析构函数
  13. }
  14. ~DisposableClass()
  15. {
  16. Dispose(false);
  17. }
  18. private void Dispose(bool disposing)
  19. {
  20. if(_disposed) return; //如果已经被回收,就中断执行
  21. if(disposing)
  22. {
  23. //TODO:释放本对象中管理的托管资源
  24. }
  25. //TODO:释放非托管资源
  26. _disposed = true;
  27. }
  28. }

可能存在的疑问

  • 既然 gc 是另外一线程执行的,为什么Dispose(bool)函数里不加锁?

    因为如果可以主动调用的时候,肯定此对象不是死对象,也不会被回收,因此不会同时调用到哪里不太对,但又说不上来。

  • 为什么析构函数调用的dispose(false)不释放托管资源?

    因为析构函数由 gc 来调用,gc 会依次释放所有的死对象(不可到达),释放的顺序是随机的,如果在一个对象的析构里调用了一个本次 gc已经释放的对象,就会发生释放两次的错误。