我的看法和见解,有什么不对的地方希望大家提出,还有我提出的问题,也希望大家讨论一下。(我学习这部分知识所用书籍是《.NET框架程序设计》李建中译,《effective C#》李建中译)。
1.首先说一下非托管资源。有的资源,对于.net的运行时来说,是没有办法托管的,比如文件句柄,Win32的互斥体(Mutex对象),数据库链接等。因为他们都是和操作系统或者是数据库打交道,这些资源是非.net的资源。所以像sqlConnection,Mutex,FileStream这些类,比较特殊。有的书说什么托管资源如果不调用dispose,.net就不知道怎么去释放它。 其实包含了托管资源的这些类,他们都实现了dispose和finalize,所以第一次垃圾回收(当对象不可到达之后),就会调用finalize方法,释放非托管资源,第2次再回收本来的内存。(这里不考虑代龄的问题)。
2. 下面看看书上写的Dispose的实现:class MyClass:IDisposable
    {
        private SqlConnection sqlCon = new SqlConnection();
        private bool disposed = false;        public void Dispose()
        {
            Dispose(true);
            GC.suppressFinalize(this);
        }
        ~MyClass()
        {
            Dispose(false);
        }
        protected virtual void Dispose(bool disposing)
        {
            try
            {
                lock (this)
                {
                    if(disposed) //已经调用过Dispose了就直接返回
                       {
                        return;
                    }
                    
                    if(disposing)  //表示是手动调用Dispose的
                       {
                        //释放托管资源(很多书都这样写,我真是不懂到底什么意思,怎么去释放托管资源)
                       }
                    //书中说下面的代码是finalize和dispose都要执行的代码,因为finalize是垃圾回收的时候执行
                       //类中的内部对象可能在该finalize执行的时候已经被回收了,所以不应该引用其他内部对象
                       sqlCon.Dispose();                }
            }
            finally
            {
                sqlCon.Dispose();
                disposed = true;
            }
        }这里的问题: 在if(disposing){} 中,到底书上说的释放托管资源是什么意思呢?其实这里可以访问对象中的任何资源这个我是知道的,因为是手动调用嘛,所有东西都是活的。
但是sqlCon的调用,为什么可以写在if(disposing)的外面呢? 因为MyClass的finalize调用的时候,sqlCon一定没有释放吗?其实我测试过,如果一个实现了finalize的类中有另一个也实现了finalize的对象,内部对象的finalize通常都会先执行。而sqlCon也是实现了finalize的,所以我想sqlCon的finalize也一定实现执行,只是我们看不到。当然,执行了finalize,只是这个对象调用了一个函数而已,其实它的内存还在,所以调用dispose再让它做点事,是可以的(因为sqlCon的finalize已经执行了,所以sqlCon.Dispose方法在这种情况下是直接返回的,没有做任何事情,因为sqlCon的非托管资源已经释放了)。 
那我现在的情况是这样的 , MyClass中包含了一个对象 b, 而b中又包含了一个sqlConnection对象,b实现了标准的dispose方,如下:
class B:IDisposable  (具体如何实现就不写了)
{
    sqlConnection sqlCon;
}
MyClass:Idisposeble
{
    b;
    protected virtual void Dispose(bool disposing)
    {
      // ....其他部分省略
       if(disposing)
      {
           .....
      } 
      b.Dispose(true),应该在这里调用吗?
                         也就是因为b包含了非托管的资源,所以做法和一般的sqlConnection相同
    } 
}我这么说对吗? b.Dispose(true)应该放在if(disposing)块的外面。???????????????所以,所谓的if(disposing)块之外,不应该访问类的内部成员,应该是错的吧,应该是只应该做资源的释放,也就是只应该调用Dispose方法。 原因就是因为这些对象虽然可能已经执行过finalize了. 而如果是没有实现Finalize方法的对象,他们有可能已经是null了。 
可能我的叙述有点混乱 ,但是还是希望大家能讨论下。

解决方案 »

  1.   

    更正一下,2的第1个程序中,sqlCon.Dispose()应该只出现在finally块中哈, try里面不应该有 我写错了
      

  2.   

    还有                if(disposed) //已经调用过Dispose了就直接返回
                        {
                         return;
                      }也不应该放在try中。
      

  3.   

    如果一个对象的基类实现 IDisposable,该对象还必须调用其基类的 Dispose 方法。所以你的protected virtual void Dispose(bool disposing); 这个方法是不是有问题?
    按你写的貌似应该是:private void Dispose(bool disposing)
    {
       try
                {
                    lock (this)
                    {
                        if(disposed) //已经调用过Dispose了就直接返回
                          {
                            return;
                        }
                       
                        if(disposing)  //表示是手动调用Dispose的
                          {                         
                            //释放托管资源,如果是你自己定义的类型应该在这释放,datatable也应该在这 
                            // 设有别个类被实例化   YouClass c=new YouClass();//在释放前有实例化的,并且 YouClass同样实现了//IDisposable
                             c.dispose();
                          }
                          //非托管对象释放,
                          sqlCon.Dispose();
                        
                    }
                }
                finally
                {
                     disposed = true;
                } 
    }
      

  4.   

    看MSDN实例,注释比较详细using System;
    using System.ComponentModel;// The following example demonstrates how to create
    // a resource class that implements the IDisposable interface
    // and the IDisposable.Dispose method.public class DisposeExample
    {
        // A base class that implements IDisposable.
        // By implementing IDisposable, you are announcing that 
        // instances of this type allocate scarce resources.
        public class MyResource: IDisposable
        {
            // Pointer to an external unmanaged resource.
            private IntPtr handle;
            // Other managed resource this class uses.
            private Component component = new Component();
            // Track whether Dispose has been called.
            private bool disposed = false;        // The class constructor.
            public MyResource(IntPtr handle)
            {
                this.handle = handle;
            }        // Implement IDisposable.
            // Do not make this method virtual.
            // A derived class should not be able to override this method.
            public void Dispose()
            {
                Dispose(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);
            }        // Dispose(bool disposing) executes in two distinct scenarios.
            // If disposing equals true, the method has been called directly
            // or indirectly by a user's code. Managed and unmanaged resources
            // can be disposed.
            // If disposing equals false, the method has been called by the 
            // runtime from inside the finalizer and you should not reference 
            // other objects. Only unmanaged resources can be disposed.
            private void Dispose(bool disposing)
            {
                // Check to see if Dispose has already been called.
                if(!this.disposed)
                {
                    // If disposing equals true, dispose all managed 
                    // and unmanaged resources.
                    if(disposing)
                    {
                    // Dispose managed resources.
                    component.Dispose();
                    }
          
                    // Call the appropriate methods to clean up 
                    // unmanaged resources here.
                    // If disposing is false, 
                    // only the following code is executed.
                    CloseHandle(handle);
                    handle = IntPtr.Zero;
                }
                disposed = true;         
            }        // Use interop to call the method necessary  
            // to clean up the unmanaged resource.
            [System.Runtime.InteropServices.DllImport("Kernel32")]
            private extern static Boolean CloseHandle(IntPtr handle);        // Use C# destructor syntax for finalization code.
            // This destructor will run only if the Dispose method 
            // does not get called.
            // It gives your base class the opportunity to finalize.
            // Do not provide destructors in types derived from this class.
            ~MyResource()      
            {
                // Do not re-create Dispose clean-up code here.
                // Calling Dispose(false) is optimal in terms of
                // readability and maintainability.
                Dispose(false);
            }
        }
        public static void Main()
        {
            // Insert code here to create
            // and use the MyResource object.   
        }
    }
      

  5.   

    对以 ojekleen 的说法, 我不敢苟同。
    /////////////////////////////////////
    你说的 YouClass类型的对象c,也实现了 Idisposable接口,那么它内部一定是拥有非托管的资源,比如FileStream.
    那么如果像你说的private void Dispose(bool disposing)
    {
       try
                {
                    lock (this)
                    {
                        if(disposed) //已经调用过Dispose了就直接返回
                          {
                            return;
                        }
                       
                        if(disposing)  //表示是手动调用Dispose的
                          {                         
                            //释放托管资源,如果是你自己定义的类型应该在这释放,datatable也应该在这 
                            // 设有别个类被实例化   YouClass c=new YouClass();//在释放前有实例化的,并且 YouClass同样实现了//IDisposable
                             c.dispose();
                          }
                          //非托管对象释放,
                          sqlCon.Dispose();
                        
                    }
                }
                finally
                {
                     disposed = true;
                } 
    }这个c.dispose()在 if(disposing)之内, 那么如果是垃圾回收调用的finalize,那么这个非托管资源在这次就得不到释放哦?  按照本意,一个类的dispose手动调用,就是为了把该对象之内的所有非托管资源都释放(因为非托管资源很宝贵)。 如果像你这样做,根本就没有起到dispose的作用。 比如在MyClass中,有100个YouClass的对象,而YouClass对象里,封装了sqlConnection对象。 如果像你所说的,那么这个YourClass的diapose方法手动调用的话,  根本就没有进行任何行为,100个数据库链接还是要垃圾回收的时候才 会调用其finalize方法。
      

  6.   

    其实最终的问题就是 一个类a中包含了一个对象b,而b中又包含了对象sqlConnection,那么就是a中包含了非托管的资源,只是被b封装了一层,当然 b正确的实现了Dispose和Finalize。 那么在a的dispose中,b应该当做什么来对待呢? 我觉得b也应该和一般的 IntPtr一样,当做包含了非托管资源的类 。 那么b.dispose应该在 a的dispose方法里的 if(disposing)之外调用,也就是不管是自动调用的 还是手动调用的,都应该有 b.dispose。 
    不然为什么sqlConnection的dispose可以写在 if(disposing)之外, 而b.disposing却不可以? 公共运行时对待sqlConnection和b的做法应该是一样的。他们都拥有非托管资源,他们都实现了IDisposable接口和都重写了Finalize方法。
      

  7.   

    其实放在if(disposing)之内的东西, 是害怕放在之外,该东西已经被释放了。 那么为什么sqlConnection可以放在之外呢?它不会被释放吗?它也实现了Finalize函数,也会自己释放的。 只是第一次回收不会释放,只会调用其finalize函数。 所以我所说的b,也实现了finalize函数,那么它也应该和sqlConnection一样的对待,也是应该放在if(disposing)块之外才对。 不然在手动调用主类的dispose的时候,内部的b对象中的sqlConnection还是没被释放,那么这个手动的Dispose有什么意义呢?难道被封装了一层或者多层的非托管资源,就一定只能在垃圾回收的时候自动调用finalize的时候才能释放吗? 我觉得是没有道理的。。 希望各位讨论下。
      

  8.   

    这个c.dispose()在 if(disposing)之内, 那么如果是垃圾回收调用的finalize,那么这个非托管资源在这次就得不到释放哦? 
    在 if(disposing)之内是托管资源吧?
    按照本意,一个类的dispose手动调用,就是为了把该对象之内的所有非托管资源都释放(因为非托管资源很宝贵)这个是对的
    比如在MyClass中,有100个YouClass的对象,而YouClass对象里,封装了sqlConnection对象。如果像你所说的,那么这个YourClass的diapose方法手动调用的话,  根本就没有进行任何行为,100个数据库链接还是要垃圾回收的时候才 会调用其finalize方法。你的例子上没有显示你说的这种情况,所以我没写,实际的应该是要释放的.即如果YouClass里面有非托管资源必须在if(disposing)之外显示disponse();
    MSDN原话为: 
    如果 Object 保存了对任何资源的引用,则 Finalize 必须由派生类重写,以便在垃圾回收过程中,在放弃 Object 之前释放这些资源。 当类型使用文件句柄或数据库连接这类在回收使用托管对象时必须释放的非托管资源时,该类型必须实现 Finalize。
      

  9.   

    16楼的是对的。 这句原话我确实没看到。  那么Component里没有非托管资源吧,那为什么它提供了dispose方法呢?在 C#高级编程里 也有句原话是 在 if(disposing)块内, cleanup the managed resource by calling their Dispose method.我确实不解。 也就是释放托管资源,只是针对有dispose方法的托管类吗? 但是为什么托管类会有dispose方法?又是哪些类呢、
      

  10.   

    对托管资源应该是通过finalize函数,只是实现了IDisposable接口的都有重写finalize,
    .net其实不建议重写finalize,因为垃圾回收过程中的回收往往比不重写需要更长的时间关于托管资源包含托管资源时,.net具体处理并不是按由内向外或者由外向内的顺便.
    垃圾回收过程中执行终结器的准确时间是不确定的。不保证资源在任何特定的时间都能释放,除非调用 Close 方法或 Dispose 方法。即使一个对象引用另一个对象,也不能保证两个对象的终结器以任何特定的顺序运行。即,如果对象 A 具有对对象 B 的引用,并且两者都有终结器,则当对象 A 的终结器启动时,对象 B 可能已经终结了。
      

  11.   

    系统是会自动会去进行垃圾回收,但是它无法做到立即释放资源,还会驻留在内存中一段时间,等内存空闲的时候再去释放它
    有时候当你一个页面关闭想重新加载时,这时这些缓存就会影响到它,所以就要用大disposing立即释放
      

  12.   

    disposing 标志 false 表明是系统行为, 这个时候 finalize 的调用顺序是不确定的, 可能你的 component 中有些已经释放掉了, 所以在这个过程你再调用该 component 的 dispose 是错误动作, 所以应该把一切都交给系统去做http://davybrion.com/blog/2008/08/net-memory-management/
    看看这篇文章吧, 讲的很清楚了
      

  13.   


    如果 disposing 等于 true,则该方法已由用户的代码直接调用或间接调用,并且可释放托管资源和非托管资源。如果 disposing 等于 false,则该方法已由运行库从终结器内部调用,并且只能释放非托管资源。因为终结器不会以任意特定的顺序执行,所以当对象正在执行其终止代码时,不应引用其他对象。如果正在执行的终结器引用了另一个已经终止的对象,则该正在执行的终结器将失败。所以你的b.Dispose(true) 应该放在if(disposing) 块中。而且不能放在外面,否则该正在执行的终结器将失败。另外,你理解错了,虽然sqlConnection包含非托管资源,但和你的类没有任何关系,你的类包含sqlConnection实例并不表示你也包含非托管资源,
    因为sqlConnection的实例是托管对象,它不是非托管资源。当然上面也说了,你也可以实现Dispose来释放托管资源,但建议你只有在sqlConnection的实例在整个类的所有实例方法中都引用的时候才这么做,也就是SQL链接从类初始化就打开,知道类的实例被释放才关闭SQL数据库链接,但你也应该知道这样做将导致一个长期打开的数据库连接,严重影响性能。合适的做法是在使用到SQL链接的方法中立即打开和立即关闭,这样情况下不用实现Dispose。
      

  14.   

    类实例经常封装对不受运行库管理的资源(如窗口句柄 (HWND)、数据库连接等)的控制。因此,应该既提供显式方法也提供隐式方法来释放这些资源。通过在对象上实现受保护的 Finalize 方法(在 C# 和 C++ 中为析构函数语法)可提供隐式控制。当不再有任何有效的对象引用后,垃圾回收器在某个时间调用此方法。 在有些情况下,您可能想为使用该对象的程序员提供显式释放这些外部资源的能力,以便在垃圾回收器释放该对象之前释放这些资源。当外部资源稀少或者昂贵时,如果程序员在资源不再使用时显式释放它们,则可以获得更好的性能。若要提供显式控制,请实现由 IDisposable 接口提供的 Dispose 方法。在完成使用该对象的操作时,该对象的使用者应调用此方法。即使对对象的其他引用是活动的,也可以调用 Dispose。注意,即使在通过 Dispose 提供显式控制时,也应该使用 Finalize 方法提供隐式清理。Finalize 提供了候补手段,可防止在程序员未能调用 Dispose 时造成资源永久泄漏。
    所以只要你按照NET的设计规范来设计类,那么托管资源永远都会被释放。Dispose(true)只是为了显示释放,以提高应用程序性能:
    所以
    if(disposing)  //表示是手动调用Dispose的 

       //释放托管资源(很多书都这样写,我真是不懂到底什么意思,怎么去释放托管资源) 

    这是正确的,假垃圾程序员没有显示调用Dispose,之后Finalize 调用了Dispose,即Dispose(false),那么没关系,if(disposing)中的托管资源没释放没关系(说不定已经释放了,应为Finalize执行之后类的成员对象已经执行了Finalize),因为if(disposing)都是托管对象,没白没?托管对象啊,垃圾回收器始终会释回收它的,虽然这样会影响性能,可谁叫你不显示调用Dispose???
      

  15.   

      if(disposed) //已经调用过Dispose了就直接返回
                          {
                            return;
                        }
                       
                        if(disposing)  //表示是手动调用Dispose的
                          {                         
                            //释放托管资源,如果是你自己定义的类型应该在这释放,datatable也应该在这 
                            // 设有别个类被实例化   YouClass c=new YouClass();//在释放前有实例化的,并且 YouClass同样实现了//IDisposable
                             c.dispose();
                          }
                          //非托管对象释放,
                          sqlCon.Dispose();
                        
                    }
    6楼的逻辑是正确的,即在if(disposing)块释放托管资源,在外面释放非托管资源,但可能是比误,其实 sqlCon 是托管对象了,也应该在if(disposing)里面
      

  16.   

    楼上所说的  sqlCon是托管对象。  那么书中所说的 在  if(disposing)之外,释放非托管资源。 那么是什么意思呢? 如何释放非托管资源?
      

  17.   

    其实按8楼上的MSDN例子是对的在disposing块里释放托管资源 在块外释放非托管资源(自己持有的非托管资源) 
    之所以在disposing块之内释放托管资源是因为这个块是用户显式调用时执行的 可以保证所包含的托管资源有效(而不是正在或已经被垃圾收集)
    而因为垃圾收集顺序的不确定性 在析构函数中不宜调用true的dispose方法 因为这是很有可能你要释放的托管对象已经被垃圾收集了一次为null了
      

  18.   


    在  if(disposing)之外,释放非托管资源,这么清楚还没明白?
    比如你使用API OpenProcess 打开了一个进程并得到了改进程的句柄,那么按照SDK建议,你使用完了之后应该关闭这个进程,所以你可以在 if(disposing)之外 使用  CloseHandle 来关闭它。