代码改变世界

Finalize 和Dispose方式的理解

2011-09-12 22:05  一一九九  阅读(709)  评论(0编辑  收藏  举报

 

由于.net中存在托管资源和非托管资源的释放,两种不同类型的资源释放的方式是不一样的,而由于.net释放非托管资源的时候,如果是在析构函数中释放的话,会导致GC的双倍访问,资源的生命周期会延长,系统内存占用也会增多,而如果用户手动的释放非托管资源的时候,此时GC不需要在访问析构函数,也就不需要将对象的生命周期提升了,此时,出现的一个问题是:存在两种不同的释放资源的方式,如果在这两种方式中进行协调?

这里需要解决的问题或者需要保证的效果有:

  • 防止资源的重复释放。如果已经通过Dispose方式或者Finalize方式中的一种释放了资源,就不需要通过另外方式来释放资源,避免资源的重复释放。
  • 防止资源的漏放。提供一种保底的方式,防止其中一种方式没有执行的话,另外一种方式保证执行的情况。
  • 由于一方面保证资源的重复释放,一方面又要防止资源的漏放,所以必然存在一个执行资源释放的过程,这就需要资源重复释放的问题需要保证或者通知另外一种方式不再执行。
  • 确保各自的职责。 Dispose和Finalize方式只能释放自己能够释放的资源,不能够释放不能释放的资源

关于在类中加入析构函数为啥会导致GC效率问题的可以参见:

http://www.cnblogs.com/sunshinefly128/archive/2011/09/11/2173628.html

代码如下:

public class Base: IDisposable
{
    private bool disposed = false;
    //implement IDisposable
    public void Dispose()
    {
       //确定是手动的释放资源
       this.Dispose(true);
       //通知GC虽然此类有析构函数,但是不需要将此类实例纳入到Finalize 队列中,直接释放即可。
       GC.SuppressFinalize(this);
      }
  
   public void Dispose(bool disposing)
   {
      if(!disposed)
      {
         if(disposing)
         {
            //释放托管资源,为什么这个地方会存在这种说法呢? 不是说托管资源都是CLR来释放的?这个问题是由于当此类被其他类引用的时候,而又明确要将此类释放掉的话,需要将此类和其他类的引用关系去除掉,这样CLR才能够释放此类。
              this.XXXArrray.Remove(this);//for example
           }
          //释放非托管资源。
           XXX.dispose();
         this.disposed = true;
        }
    }
    public ~Base()
    {
        //确保即使客户没有调用Dispose,也能够将资源释放掉。同时表明非手动释放。
        this.Dispose(false);
       }
}

另外考虑到类的继承层次的问题,需要保证子类调用到基类的释放函数。

// Design pattern for a derived class.
public class Derived: Base
{
    private bool disposed = false;
 
    protected override void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Release managed resources.
            }
            // Release unmanaged resources.
            // Set large fields to null.
           // Call Dispose on your base class.
            disposed = true;
        }
        base.Dispose(disposing);
    }
    // The derived class does not have a Finalize method
    // or a Dispose method without parameters because it inherits
    // them from the base class.
}

摘要一下MSDN提到的几条规则:

下面的规则概括了 Finalize 方法的使用准则:

  • 仅在要求终结的对象上实现 Finalize。 存在与 Finalize 方法相关的性能开销。

  • 如果需要 Finalize 方法,应考虑实现 IDisposable,以使类的用户可以避免因调用 Finalize 方法而带来的开销。

  • 不要提高 Finalize 方法的可见性。 该方法的可见性应该是 protected,而不是 public

  • 对象的 Finalize 方法应该释放该对象拥有的所有外部资源。 此外,Finalize 方法应该仅释放由该对象控制的资源。 Finalize 方法不应该引用任何其他对象。

  • 不要对不是对象的基类的对象直接调用 Finalize 方法。 在 C# 编程语言中,这不是有效的操作。

  • 应在对象的 Finalize 方法中调用基类的 Finalize 方法。

    注意 注意

    基类的 Finalize 方法通过 C# 和 C++ 析构函数语法自动进行调用。

释放


下面的规则概括了 Dispose 方法的使用准则:

  • 在封装明确需要释放的资源的类型上实现释放设计方案。 用户可以通过调用公共 Dispose 方法释放外部资源。

  • 在通常包含控制资源的派生类型的基类型上实现释放设计方案,即使基类型并不需要也如此。 如果基类型有 Close 方法,这通常指示需要实现 Dispose。 在这类情况下,不要在基类型上实现 Finalize 方法。 应该在任何引入需要清理的资源的派生类型中实现 Finalize

  • 使用类型的 Dispose 方法释放该类型所拥有的所有可释放资源。

  • 对实例调用了 Dispose 后,应通过调用 GC.SuppressFinalize 禁止 Finalize 方法运行。 此规则的一个例外是当必须用 Finalize 完成 Dispose 没有完成的工作的情况,但这种情况很少见。

  • 如果基类实现了 IDisposable,则应调用基类的 Dispose 方法。

  • 不要假定 Dispose 将被调用。 如果 Dispose 未被调用,也应该使用 Finalize 方法释放类型所拥有的非托管资源。

  • 当资源已经释放时,在该类型上从实例方法(非 Dispose)引发一个 ObjectDisposedException。 该规则不适用于 Dispose 方法,该方法应该可以在不引发异常的情况下被多次调用。

  • 通过基类型的层次结构传播对 Dispose 的调用。 Dispose 方法应释放由此对象以及此对象所拥有的任何对象所控制的所有资源。 例如,可以创建一个类似 TextReader 的对象来控制 Stream 和 Encoding,两者均在用户不知道的情况下由 TextReader 创建。 另外,Stream 和 Encoding 都可以获取外部资源。 当对 TextReader 调用 Dispose 方法时,它应继而对 Stream 和 Encoding 调用 Dispose,使它们释放其外部资源。

  • 考虑在调用了某对象的 Dispose 方法后禁止对该对象的使用。 重新创建已释放的对象是难以实现的方案。

  • 允许 Dispose 方法被调用多次而不引发异常。 此方法在首次调用后应该什么也不做

参考:http://msdn.microsoft.com/zh-cn/library/b1yfkh5e%28v=VS.100%29.aspx