与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继承于DbConnection , DbConnection继承于Component,Component里实现了
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;
}
}
}