与c++一样,c#可以定义析构函数。但c#的析构函数主要用于释放非托管资源。在Net中,由GC垃圾回收线程掌握对象资源的释放,程序员无法掌控析构函数的调用时机。为了完全掌控非托管资源的释放,Net提供一个IDisposable接口。
一、c#的析构函数
1.1 定义析构函数
class Program { static void Main(string[] args) { } ~Program()//析构函数 { } }
1.2 编译器把析构函数解析成Finalize终结器方法
Finalize的具体实现
.method family hidebysig virtual instance void Finalize() cil managed{ // Code size 14 (0xe) .maxstack 1 .try { IL_0000: nop IL_0001: nop IL_0002: leave.s IL_000c } // end .try finally { IL_0004: ldarg.0 IL_0005: call instance void [mscorlib]System.Object::Finalize() IL_000a: nop IL_000b: endfinally } // end handler IL_000c: nop IL_000d: ret} // end of method Program::Finalize
如同构造函数一样,析构函数会调用基类的析构函数。与构造函数相反的是,析构函数先完成自己的清理工作,再调用基类的析构函数。
二、IDisposable模式的实现
如何安全、快速的释放非托管资源?如果我们的类使用的非托管资源,如数据库连接、文件句柄,这些资源需做到:使用后立刻释放。如果我们只在析构函数实现释放,则无法达到使用后立刻释放这个目标。为了实现这个目标,Net引入了IDisposable模式。
IDisposable的接口定义如下
public interface IDisposable { // Summary: // Performs application-defined tasks associated with freeing, releasing, or // resetting unmanaged resources. void Dispose(); }
我们来分析一下SqlConnection类的IDisposable模式的实现
SqlConnection继承于 , 继承于,里实现了
IDisposable接口,Component代码如下:
public class Component : MarshalByRefObject, IComponent, IDisposable{ // Methods public void Dispose() { this.Dispose(true);释放托管资源 GC.SuppressFinalize(this);//请求系统不要调用指定对象的终结器. //该方法在对象头中设置一个位,系统在调用终结器时将检查这个位 } protected virtual void Dispose(bool disposing) { if (disposing) { lock (this) { if ((this.site != null) && (this.site.Container != null)) { this.site.Container.Remove(this); } if (this.events != null) { EventHandler handler = (EventHandler) this.events[EventDisposed]; if (handler != null) { handler(this, EventArgs.Empty); } } } } } ~Component() { this.Dispose(false);//有可能开发人员忘记调用Dispose方法,导致资源泄漏,为了避免这种情况,在析构函数调用Dispose,确保资源被释放 } }
在SqlConnection中重写了protected virtual void Dispose(bool disposing)方法,代码如下
protected override void Dispose(bool disposing) { if (disposing) { this._userConnectionOptions = null;//把本对象引用的其他对象设置成空 this._poolGroup = null; this.Close();//关闭数据库连接 } this.DisposeMe(disposing); base.Dispose(disposing);//调用基类的Dispose }
三、正确调用Dispose方法
SqlConnection conn = new SqlConnection(); //do something; conn.Dispose();
这代码有漏洞,当do something出现异常时,导致Dispose执行不了,更安全的做法是:
class Program { static void Main(string[] args) { SqlConnection conn = new SqlConnection(); try { //do something; } finally { conn.Dispose(); } } }
为了简化代码量,c#使用using简化输入,编译器自动翻译成 try...finally
class Program { static void Main(string[] args) { using(SqlConnection conn = new SqlConnection()) { //do something; } } }