C# IDisposable
C#中昂贵资源的释放是通过手工调用IDisposable.Dispose方法来进行的,更有using语句可以帮助用户及时调用Dispose方法,这在存在异常抛出的情况下非常有用。
MSDN对IDisposable接口的解释为:定义一种释放分配的资源的方法。此接口的主要用途是释放非托管资源。当不再使用托管对象时,垃圾回收器会自动释放分配给该对象的内存。但无法预测进行垃圾回收的时间。另外,垃圾回收器对窗口句柄或打开的文件和流等非托管资源一无所知。将此接口的 Dispose 方法与垃圾回收器一起使用来显式释放非托管资源。当不再需要对象时,对象的使用者可以调用此方法。
通常要实现自己的IDisposable子类时,写法如下(其中的Dispose方法可以Alt+Shift+F10自动生成的):
class Demo : System.IDisposable
{
#region IDisposable 成员
public void Dispose()
{
throw new System.NotImplementedException();
}
#endregion
}
对Demo的使用方法如下:
Demo demo = new Demo();
// do something, but throw exceptions.
demo.Dispose();
当然为了防止Dispose方法忘记调用,也可以不厌其烦的写上:
Demo demo = new Demo();
try
{
//
}
finally
{
demo.Dispose();
}
using (Demo demo = new Demo())
{
// do anything, even throw an exception.
}
这样既省了手工调用Dispose方法的麻烦,也能在using语句块中异常抛出时保证Dispose方法正常被调用(这有点让人想起lock语句)。忘记Dispose方法的调用会造成内存泄露(别以为C#就不漏了)
更高级的IDisposable实现参考以下代码,可以精确控制资源回收:
class Demo : System.IDisposable
{
private bool disposed = false; // 保证多次调用Dispose方式不会抛出异常
#region IDisposable 成员
//可以被客户直接调用
public void Dispose()
{
//必须以Dispose(true)方式调用,以true告诉Dispose(bool disposing)函数是被客户直接调用的
Dispose(true); // MUST be true
// This object will be cleaned up by the Dispose method. Therefore, you should call GC.SupressFinalize to take this object off the finalization queue and prevent finalization code for this object from executing a second time.
GC.SuppressFinalize(this); // 告诉垃圾回收器从Finalization队列中清除自己,从而阻止垃圾回收器调用Finalize方法.
}
#endregion
// 无法被客户直接调用
// 如果 disposing 是 true, 那么这个方法是被客户直接调用的,那么托管的,和非托管的资源都可以释放
// 如果 disposing 是 false, 那么函数是从垃圾回收器在调用Finalize时调用的,此时不应当引用其他托管对象所以,只能释放非托管资源protected virtual void Dispose(bool disposing)
{
// If you need thread safety, use a lock around these operations, as well as in your methods that use the resource.
// Check to see if Dispose has already been called
if (disposed)
return;
if (disposing) // 这个方法是被客户直接调用的,那么托管的,和非托管的资源都可以释放
{
// 在这里释放托管资源
}
// 在这里释放非托管资源
disposed = true; // Indicate that the instance has been disposed
}
// 析构函数自动生成 Finalize 方法和对基类的 Finalize 方法的调用.默认情况下,一个类是没有析构函数的,也就是说,对象被垃圾回收时不会被调用Finalize方法
~Demo()
{
// 为了保持代码的可读性性和可维护性,千万不要在这里写释放非托管资源的代码
// 必须以Dispose(false)方式调用,以false告诉Dispose(bool disposing)函数是从垃圾回收器在调用Finalize时调用的Dispose(false); // MUST be false
}
}
以上代码中带参数的Dispose方法可以写成private,但如果要写成protected 最好也加上virtual,以便子类定制。但绝对不能写成public的,否则很可能用户在使用时传进来错误的false参数,从而造成内存管理混乱。
不论什么类的析构函数编译后签名都变成统一的protected virtual void Finalize(), 这是编译器在后台做的手脚。GC通过Finalize方法来在回收对象前尝试释放资源并进行其他清理操作。有了Finalize方法的保护,即使用户不小心忘记调用Dispose方法,GC在自动调用Finalize时也会正常的执行资源回收,防止泄露发生。
整理一份干净的for reusing:
class MyDispose : System.IDisposable
{
private bool disposed = false;
#region IDisposable 成员
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
// TODO: 在这里释放托管资源
}
// TODO: 在这里释放非托管资源
disposed = true;
}
~MyDispose()
{
Dispose(false);
}
}