代码改变世界

IDisposable接口详解

2011-03-04 19:52  田志良  阅读(6018)  评论(7编辑  收藏  举报

1. MSDN中关于这个接口的说明

[ComVisible(true)]
public interface IDisposable
{
    // Methods
    void Dispose();
}

    [ComVisible(true)]:指示该托管类型对 COM 是可见的。此接口的主要用途是释放非托管资源。当不再使用托管对象时,垃圾回收器会自动释放分配给该对象的内存。但无法预测进行垃圾回收的时间。另外,垃圾回收器对窗口句柄或打开的文件和流等非托管资源一无所知。将此接口的Dispose方法与垃圾回收器一起使用来显式释放非托管资源。当不再需要对象时,对象的使用者可以调用此方法。

2. 基本应用

    定义一个实现了IDisposable接口的类,代码如下: 

 public class CaryClass :IDisposable
 {
    public void DoSomething()
    {
       Console.WriteLine("Do some thing....");
    }
    public void Dispose()
    {
       Console.WriteLine("及时释放资源");
    }
 }

    有两种方式来调用:

    第一种方式,使用Using语句会自动调用Dispose方法,代码如下:

  using (CaryClass caryClass = new CaryClass())
  {
      caryClass.DoSomething();
  }

    第二种方式,现实调用该接口的Dispose方法,代码如下:

   CaryClass caryClass = new CaryClass();
   try
    {
       caryClass.DoSomething();               
    }
   finally
    {
       IDisposable disposable = caryClass as IDisposable;
       if (disposable != null) disposable.Dispose();
    }
    两种方式的执行结果是一样的,如下图:

3. Disposable 模式

    在.NET种由于当对象变为不可访问后将自动调用Finalize方法,所以我们手动调用IDisposable接口的Dispose方法和对象终结器调用的方法极其类似,我们最好将他们放到一起来处理。我们首先想到的是重写Finalize方法,如下:

protected override void Finalize()
{
     Console.WritleLine("析构函数执行...");
}
    当我们编译这段代码的时候,我们发现编译器会报如下的错误:

    这是因为编译器彻底屏蔽了父类的Finalize方法,编译器提示我们如果要重写Finalize方法我们要提供一个析构函数来代替,下面我们就提供一个析构函数:

  ~CaryClass()
  {
      Console.WriteLine("析构函数执行...");
  }

    实际上这个析构函数编译器会将其转变为如下代码:

protected override void Finalize()
{
   try
   {
     Console.WritleLine("析构函数执行...");
   }
   finally
   {
     base.Finalize();
   }
}

    然后我们就可以将Dispose方法的调用和对象的终结器放在一起来处理,如下:

public class CaryClass: IDisposable
{
    ~CaryClass()
    {
        Dispose();
    }
    public void Dispose()
    {
        // 清理资源
    }
}
    上面实现方式实际上调用了Dispose方法和Finalize方法,这样就有可能导致做重复的清理工作,所以就有了下面经典Disposable 模式:
 private bool IsDisposed=false;  
 public void Dispose()  
 {  
     Dispose(true);  
     GC.SuppressFinalize(this);  
 }  
 protected void Dispose(bool Disposing)  
 {  
     if(!IsDisposed)  
     {  
         if(Disposing)  
         {  
            //清理托管资源
         }  
         //清理非托管资源
     }  
     IsDisposed=true;  
 }  
 ~CaryClass()  
 {  
     Dispose(false);  
 } 

备注:

  • SupressFinalize方法以防止垃圾回收器对不需要终止的对象调用 Object.Finalize()。 
  • 使用IDisposable.Dispose 方法,用户可以在可将对象作为垃圾回收之前随时释放资源。如果调用了 IDisposable.Dispose 方法,此方法会释放对象的资源。这样,就没有必要进行终止。IDisposable.Dispose 应调用 GC.SuppressFinalize 以使垃圾回收器不调用对象的终结器。
  • 我们不希望Dispose(bool Diposing)方法被外部调用,所以他的访问级别为protected 。如果Diposing为true则释放托管资源和非托管资源,如果 Diposing等于false则该方法已由运行库从终结器内部调用,并且只能释放非托管资源。 
  • 如果在对象被释放后调用其他方法,则可能会引发 ObjectDisposedException。

4. 实例解析

    下面代码对Dispose方法做了封装,说明如何在使用托管和本机资源的类中实现 Dispose(bool) 的常规示例:

public class BaseResource : IDisposable
    {
        // 非托管资源
        private IntPtr handle;
        //托管资源
        private Component Components;
        // Dispose是否被调用
        private bool disposed = false;

        public BaseResource()
        {            
        }
       
        public void Dispose()
        {
            Dispose(true);            
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
           
            if (!this.disposed)          
            {               
                if (disposing)
                {
                    // 释放托管资源
                    Components.Dispose();
                }
                // 释放非托管资源,如果disposing为false, 
                // 只有托管资源被释放
                CloseHandle(handle);
                handle = IntPtr.Zero;
                // 注意这里不是线程安全的
            }
            disposed = true;
        }

        // 析构函数只会在我们没有直接调用Dispose方法的时候调用
        // 派生类中不用在次提供析构函数
        ~BaseResource()
        {
            Dispose(false);
        }

        // 如果你已经调用了Dispose方法后在调用其他方法会抛出ObjectDisposedException
        public void DoSomething()
        {
            if (this.disposed)
            {
                throw new ObjectDisposedException();
            }
        }
    }

    
    public class MyResourceWrapper : BaseResource
    {
        // 托管资源
        private ManagedResource addedManaged;
        // 非托管资源
        private NativeResource addedNative;
        private bool disposed = false;
       
        public MyResourceWrapper()
        {           
        }

        protected override void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                try
                {
                    if (disposing)
                    {                        
                        addedManaged.Dispose();
                    }
                    
                    CloseHandle(addedNative);
                    this.disposed = true;
                }
                finally
                {                   
                    base.Dispose(disposing);
                }
            }
        }
    }

    使用CLR垃圾收集器,您不必再担心如何管理对托管堆分配的内存,不过您仍需清理其他类型的资源。托管类通过 IDisposable 接口使其使用方可以在垃圾收集器终结对象前释放可能很重要的资源。通过遵循 disposable 模式并且留 意需注意的问题,类可以确保其所有资源得以正确清理,并且在直接通过 Dispose 调用或通过终结器线程运行清理代码时 不会发生任何问题。