C#中IDispose接口的实现及为何这么实现详解

2019-12-30 19:45:14王振洲

注意:GC最终虽然会释放托管资源,但是GC并不知道或者关心你的Dispose方法。那仅仅是一个我们选择的名字。

GC调用析构函数是一个完美的时机来释放托管资源,我们实现析构函数的功能通过重写Finalize方法。

注意:在C#中,你不能真的去使用重写虚函数的方法去重写Finalize方法。你只能使用像C++的析构函数的语法去编写,编译器会自动对你的代码进行一些改动来实现override终结器方法。(具体可查看MSDN中析构函数一节)


~MyObject() 
{ 
 //we're being finalized (i.e.destroyed), call Dispose in case the user forgot to 
 Dispose(); //<--Warning:subtle bug! Keep reading! 
} 

但是这有一个Bug。

试想一下,你的GC是在后台线程调用,你对GC的调用时间和对垃圾对象的释放顺序根本无法掌控,很有可能的发生的是,你的对象的某些托管资源已经在调用终结器之前就被释放了,当GC调用终结器时,最终会释放两次托管资源!


public void Dispose() 
{ 
 //Free unmanaged resources 
 Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle); 
 
 //Free managed resources too 
 if (this.databaseConnection !=null) 
 { 
 this.databaseConnection.Dispose(); //<-- crash, GC already destroyedit 
 this.databaseConnection =null; 
 } 
 if (this.frameBufferImage !=null) 
 { 
 this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it 
 this.frameBufferImage = null; 
 } 
} 

所以你需要做的是让终结器告诉Dispose方法,它不应该再次释放任何托管资源了(因为这些资源很有可能已经被释放了!)

标准的Dispose模式是让Dispose函数和Finalize方法通过调用第三种方法,这个方法有一个bool类型的形参来表示此方法的调用是通过Dispose还是GC调用终结器。在这个方法里实现具体的释放资源代码。

这个第三种方法的命名当然可以随意命名,但是规范的方法签名是:


protected void Dispose(Boolean disposing) 

当然,这个参数的名字会让人读不懂什么意思。(很多网上教程都使用这个名字,第一次看很难看懂这个变量的作用)

这个参数也许可以这样写…


protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects) 
{ 
 //Free unmanaged resources 
 Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); 
 
 //Free managed resources too, butonly if I'm being called from Dispose 
 //(If I'm being called fromFinalize then the objects might not exist 
 //anymore 
 if(itIsSafeToAlsoFreeManagedObjects) 
 {  
  if (this.databaseConnection !=null) 
  { 
  this.databaseConnection.Dispose(); 
   this.databaseConnection =null; 
  } 
  if (this.frameBufferImage !=null) 
  { 
  this.frameBufferImage.Dispose(); 
   this.frameBufferImage =null; 
  } 
 } 
}