托管资源一般是指被CLR控制的内存资源,它们的分配,使用及释放由CLR来管理。例如程序中分配的对象,作用域内的变量等。而非托管资源是CLR不能控制或者管理的部分,在应用程序中使用完这些非托管资源之后,必须显示的释放他们,否则会占用系统的内存和资源,而且可能会出现意想不到的错误。
常见的非托管资源:ApplicationContext, Brush, Component, ComponentDesigner, Container, Context, Cursor, FileStream, Font, Icon, Image, Matrix, Object, OdbcDataReader, OleDBDataReader, Pen, Regex, Socket, StreamWriter, Timer, Tooltip 等等资源。
释放非托管资源的方法:Object.Finalize()以及实现IDisposable接口。
Finalize:
使用非托管资源时一般应该使用Dispose方法,让使用者显示调用以释放。但为了防止用户没有显示调用Dispose方法而不发生资源泄露,必须实现Finalize,但Finalize由系统调用,所以将Finalize的围限制为 protected,以防止用户直接调用。用户不要直接调用非基类类的 Finalize 方法。执行 方法会降低性能。
Dispose:
实现IDisposable.Dispose()方法需要完成以下目标:
- 释放掉所有非托管资源
- 释放掉所有托管资源,包括释放事件监听程序
- 设置一个安全的标记来标识对象已经被处理。
- 跳过终结操作,调用即可。
Dispose 方法应为它要释放的对象调用 SuppressFinalize 方法。如果对象当前在终止队列中,则 会阻止调用其 Finalize 方法。 请记住,执行 方法会降低性能。 在较长的 Dispose 方法末尾最好调用 方法,KeepAlive 方法的目的是确保对对象的引用存在,该对象有被垃圾回收器过早回收的危险。
实现资源释放的标准方法:
编写一个受保护的虚辅助方法,将销毁和析构共同的工作提取出来,并让派生类也可以释放其自己的资源。基类包含了核心接口的代码,而虚方法则为派生类提供了根据Dispose()或终结器的需要进行资源清理的入口:
protected virtual void Dispose( bool isDisposing );
该重载方法需要同时支持终结器和Dispose方法,同时因为它是个虚方法所以所有的派生类都可以将其作为释放资源的入口点。派生类可覆写该方法,并在其中清理自身的资源,然后调用基类的版本。当isDisposing为true时你可能同时清理托管资源和非托管资源,当isDisposing为false时你只能清理非托管资源。两种情况下,都可以调用基类的Dispose(bool)方法让它去清理它自己的资源。
////// 实现Dispose /// public class MyResourceHog : IDisposable { //已释放标记 private bool _alreadyDisposed = false; // Finalizer: 调用Dispose虚方法 ~MyResourceHog() { Dispose(false); } // 实现IDisposable接口 // 调用Dispose虚方法 // 跳过Finalizer public void Dispose() { Dispose(true); GC.SuppressFinalize(true); } // Dispose虚方法 protected virtual void Dispose(bool isDisposing) { //如果已经释放. if (_alreadyDisposed) return; if (isDisposing) { // TODO: 释放托管资源 } // TODO: 释放非托管资源 // 设置释放标记 _alreadyDisposed = true; } } ////// 继承于MyResourceHog /// public class DerivedResourceHog : MyResourceHog { private bool _disposed = false; protected override void Dispose(bool isDisposing) { if (_disposed) return; if (isDisposing) { // TODO: 释放托管资源 } // TODO: 释放非托管资源 // 释放基类资源 // 基类负责调用GC.SuppressFinalize( ) base.Dispose(isDisposing); _disposed = true; } }
注:
在托管环境里,不必为每一个创建的类写析构函数,只有需要释放使用的非托管资源,或者类中包含实现IDisposable接口的成员时添加。
对于任何与资源清理相关的方法,你必须只释放资源! 不要在处理过程中添加任何其它的任务。析构函数除了清理非托管资源什么也不应该做。
利用using和try-finally来释放资源
为非托管资源实现了Dispose方法,在程序中使用了非托管资源后就应该及时调用Dispose释放它。如果没有,系统最后会调用Finalize 来释放,但这样会花费更多的系统资源。确保非托管资源会释放的最好方法是使用using或者try/finally。它们可以确保即使是在执行Dispose方法前发生异常,也可执行Dispose方法。
using关键字只支持实现了IDisposable接口的类型,它只检验编译时类型是否支持IDisposable接口,不能识别运行时的对象,下面代码不能通过编译。
using(object obj = Factory.CreateResource){}
对于不确定是否不支持IDisposable接口的对象,可作如下处理,如果对象实现了IDisposable接口,就可以生成释放资源的代码。如果不支持,则生成using(null),虽然不做任何工作,但也是安全的。
object obj = Factory.CreateResource();using(obj as IDisposable){}
每个using声明都创建了一个try/finally程序块。当有多个需要释放的资源时,可直接使用try/finally来代替using,以免生成多层嵌套:
public void ExcuteCommand(string connectString, string commandString) { SqlConnection myConnection = null; SqlCommand myCommand = null; try { myConnection = new SqlConnection(connectString); myCommand = new SqlCommand(commandString, myConnection); myConnection.Open(); myCommand.ExecuteNonQuery(); } finally { if(myCommand != null) { myCommand.Dispose(); } if(myConnection != null) { myConnection.Dispose(); } } }
注意上面代码中判断对象是否为空!
不要如下使用,因为如果SqlCommand()的构造函数抛出异常,那么SqlConnection对象将不可能被处理了。
public void ExcuteCommand(string connectString, string commandString) { SqlConnection myConnection = new SqlConnection(connectString); SqlCommand myCommand = new SqlCommand(commandString, myConnection); using(myConnection as IDisposable) { using(myCommand as IDisposable) { myConnection.Open(); myCommand.ExecuteNonQuery(); } } }
某些类型不仅有Dispose()方法,还有Close()方法,例如SqlConnection。Dispose()比Close()的工作做的更加彻底。