C#中资源分为托管资源和非托管资源。 托管资源由垃圾回收器控制如何释放,不需要程序员过多的考虑(当然也程序员也可以自己释放)。 非托管资源需要自己编写代码来释放。那么编写好的释放非托管资源的代码(释非代码)由谁来调用呢。有两种实现方式:
一 将释非代码放到构造函数析构函数中,由系统自动调用,系统会在资源对象不再使用了,会在某个时间调用构造函数析构函数来释放非托管资源。构造函数析构函数的目的就是用来释放或清理非托管资源的。但它有一个问题是调用的时间是系统说了算,不能在程序中自己想要调用时调用析构函数,这是C#规定的。那么就产生了第二种方式。
二 将释非代码放到另外一个函数中,当自己想调用时就调用。将释非代码放在一个方法中共用,代码如下:
1 MyClass 2 { 3 ~MyClass() 4 { 5 DisposePro(); 6 } 7 8 public void Dispose() 9 { 10 DisposePro(); 11 } 12 13 private void DisposePro() 14 { 15 // 释非代码 16 ...... 17 } 18 }
但是这样可能会产生其他问题。资源可能会被多次释放,而产生问题。系统会自动调用析构函数,自己也可能多次调用Dispose()方法。那么解决方法是使用一个全局变量作为标记,来标记资源是否已经被释放,已经释放就不再释放。代码如下:
1 MyClass 2 { 3 private bool disposed = false; 4 ~MyClass() 5 { 6 DisposePro(); 7 } 8 9 public void Dispose() 10 { 11 DisposePro(); 12 } 13 14 private void DisposePro() 15 { 16 if(disposed == false) 17 { 18 // 释非代码 19 ...... 20 } 21 disposed = true; 22 } 23 }
这样看起来似乎没有问题了。但是当调用Dispose()方法只能立即释放非托管资源,而托管资源还是需要由GC自动处理。那么为了能够做到调用Dispose()方法时也能够释放立即释放托管资源,则需要在DisposePro()方法中添加上想要释放的托管资源的释放代码(释放托管代码)。代码如下:
1 MyClass 2 { 3 private bool disposed = false; 4 ~MyClass() 5 { 6 DisposePro(); 7 } 8 9 public void Dispose() 10 { 11 DisposePro(); 12 } 13 14 private void DisposePro() 15 { 16 if(disposed == false) 17 { 18 // 释托管代码 19 ...... 20 // 释非代码 21 ...... 22 } 23 disposed = true; 24 } 25 }
这样还是有问题,当析构函数调用DisposePro()时,会调用释托管代码,可能产生问题——托管对象可能已经被GC删除了而产生问题。那么使用一个标记给DisposePro(),当是被析构函数调用时不执行释托管代码。重新命名DisposePro(),代码如下:
1 MyClass 2 { 3 private bool disposed = false; 4 ~MyClass() 5 { 6 Dispose(false); 7 } 8 9 public void Dispose() 10 { 11 Dispose(true); 12 } 13 14 private void Dispose(bool disposing) 15 { 16 if(disposed == false) 17 { 18 if(disposing == true) 19 { 20 // 释托管代码 21 ...... 22 } 23 // 释非代码 24 ...... 25 } 26 disposed = true; 27 } 28 }
用这段代码来释放资源应该没有问题了。看一下标准清理模式,代码如下:
1 MyClass:IDisposable 2 { 3 private bool disposed = false; 4 ~MyClass() 5 { 6 Dispose(false); 7 } 8 9 public void Dispose() 10 { 11 Dispose(true); 12 GC.SuppressFinalize(this); 13 } 14 15 private void Dispose(bool disposing) 16 { 17 if(disposed == false) 18 { 19 if(disposing == true) 20 { 21 // 释托管代码 22 ...... 23 } 24 // 释非代码 25 ...... 26 } 27 disposed = true; 28 } 29 }
标准清理模式中多了一句GC.SuppressFinalize(this);【该方法通知CLR不要调用该方法的析构函数,因为它已经被清理了。】如果没有这句代码,我认为不影响程序的正确性,不会发生安全问题,他只是告诉系统不要再调用构造函数了。那么为什么要加上这句代码呢?如果在调用了Dispose()之后再调用析构函数只是多此一举,所以告诉系统不要再调用了。这一点应该和性能有关系。【如果不需要构造函数就不要执行构造函数,他们会带来性能上的开销】。
参考:
C#4.0 图解教程 Daniel M. Solis
相关链接:
1. 实现 Finalize 和 Dispose 以清理非托管资源 http://msdn.microsoft.com/zh-cn/library/b1yfkh5e(v=vs.90).aspx
2. GC.SuppressFinalize 方法 http://msdn.microsoft.com/zh-cn/library/system.gc.suppressfinalize(v=vs.90).aspx