class B : IDisposable
{
    sqlConnection sqlCon = new .........
}B包含了sql连接,也就是B持有非托管资源。
B正确实现了dispose和析构函数(finalize方法)class A : IDisposable
{
     B b = new B();
      ......
     ~B()
     {
        Dispose(false);
     }
     private virtual void Dispose(bool disposing)
     {
        .........//省略其他代码
        if(disposing) //表示手动调用
        {
           b.dispose(); 
        }
        //还是b.dispose()应该写这里
     }
     
}如果是对于手动调用A的dispose方法, 那么B的dispose随便放哪里,都没有区别。
所以问题就在finalize的时候(垃圾回收的时候),两种情况:
1.  像我上面代码那样,b.dispose写在if语句里面,也就是如果是垃圾回收自动调用的,就不会运行b.dispose方法。 要注意一点,由于b是A的成员,所以在外部不引用b的情况下,A和B应该是同时出现不可到达的情况,也就是他们的finalize方法会在同一次垃圾回收的时候调用,只是先后顺序不确定(假设A和B的对象都处于统一代龄)。 所以在这种情况下,不管A和B的finalize哪个先调用,这个sql的链接都会在这次垃圾回收的时候释放,而下次垃圾回收,A和B的全部内存才会被释放掉。也就是分别调用两个类的finalize是一定的。2. b.dispose写在if语句外面,如果是手动调用就不必说了,直接就把sql链接释放了,第1次垃圾回收的时候再干掉占用的内存。
  但是如果是忘了调用A的dispose,垃圾回收的时候调用A的finalize,那么对于b,有两种情况: 
     ① b自己的finalize已经调用。这种情况下,b的dispose还是会运行,只是直接就返回了,没有任何其他意义。(其实我多次测试的情况都是b的finalize自己先调用了,也就是内部成员的finalize先调用了)。其实这种情况让我联想到另一种情况,就是A直接包含的是sqlCon,而没有B。 那么在A的finalize被自动调用的时候,其实sqlCon的finalize大多数情况下也已经自动调用了,其实那个sqlCon.Dispose(),也没起什么作用 。   
  所以这里我就提出我的疑问,微软所说的在 if(disposing)中用包含了非托管资源的类(这里的sqlCon)的Dispose方法来释放资源,真正起作用的又有多少次呢? 因为运行了几十次程序,只要是一个类包含了另一个类,两个都实现了finalize,那么内部对象的finalize总是先执行。 当然也许你要说万一内部内的后执行呢?当然这种情况的话,内部对象的dispose就起作用了。 给我的感觉就是,如果是垃圾回收来处理A,那么b.dispose基本上没派上什么用场。 而且A中包含了N个像b这种实现了finalize的对象,那么其实如果你在垃圾回收对 A 工作之前,对所有的B类型的对象没有显示的释放,那么当GC对A开始清理的时候,消耗会相当大。因为所有的内部对象都会先调用自己的finalize。(当然这只是我自己的测试结果,微软也说finalize的调用顺序是不一定的。 但是我测试多次都是这样,相信只有出现特殊点的情况,才会遇到微软说的不一定的调用顺序)。   ② b的finalize还没有调用: 这种情况当然b的dispose就很有用了。 不过就算我们不调用b的dispose,这次垃圾回收还是会调用b的finalize. 感觉没什么差别。 当然你也许会说调用finalize要比我们手动调用dispose消耗更大。 这个我也承认。 总之就是,这个b.dispose到底是放 if  语句里面 还是外面 , 确实需要考虑一下。 上面就是我的见解, 请大家批斗!!

解决方案 »

  1.   

    在你的另一个贴中我提过IComponent和IContainer是更好的管理资源的模式。组件(IComponent)直接继承于IDisposable,并提供Disposed事件以便其他人追踪它的生命周期。其中ISite让我们知道它被那个容器所包含。
    容器(IContainer)是封装和跟踪零个或更多个组件的对象。通过Components接口可以知道它所包含的组件。一个典型的IContainer在它Dispose的时候,有责任Dispose它所包含的组件。比如如下代码是微软例子的节选,我们可以看到,这个IContainer在它Dispose的时候,Dipose了它所包含的组件。class LibraryContainer : IContainer
    {
        private ArrayList m_bookList;    //...
        //...    public virtual void Dispose()
        {
            for (int i = 0; i < m_bookList.Count; ++i)
            {
                IComponent curObj = (IComponent)m_bookList[i];
                curObj.Dispose();
            }        m_bookList.Clear();
        }
    }
    组件一定是IDisposable,通过组件,可以有更清晰的模式来管理需要及时回收的资源,组件的生命周期可以更有预测性,更容易管理。
        public interface IComponent : IDisposable
        {
            ISite Site { get; set; }
            event EventHandler Disposed;
        }    public interface IContainer : IDisposable
        {
            ComponentCollection Components { get; }
            void Add(IComponent component);
            void Add(IComponent component, string name);
            void Remove(IComponent component);
        }
      

  2.   

    其实还有种情况:
    A中包含sqlConnection对象sqlCon.但是A的内存和sqlCon的内存分配在不同的代龄上,A在1代,sqlCon在0代。而当A不可到达时候的垃圾收集的时候,0代满了,1代没满,那么只会清理0代的不可到达对象。所以sqlCon的finalize被调用。第2次的时候,sqlCon的内存释放了之后,A的finalize才执行,而在A的finalize代码里,sqlCon.Dispose()已经不能访问。  为什么那么多例子里 都是直接就调用sqlCon的dispose方法,都没有考虑到sqlCon有可能已经是null了,为什么他们都不判断???? 这么多大师都没想到这个问题吗? 虽然这种概率小,但是确实可能发生
      

  3.   

    其实还有种情况: 
    A中包含sqlConnection对象sqlCon. 但是A的内存和sqlCon的内存分配在不同的代龄上,A在1代,sqlCon在0代。而当A不可到达时候的垃圾收集的时候,0代满了,1代没满,那么只会清理0代的不可到达对象。所以sqlCon的finalize被调用。第2次的时候,sqlCon的内存释放了之后,A的finalize才执行,而在A的finalize代码里,sqlCon.Dispose()已经不能访问。  为什么那么多例子里 都是直接就调用sqlCon的dispose方法,都没有考虑到sqlCon有可能已经是null了,为什么他们都不判断???? 这么多大师都没想到这个问题吗? 虽然这种概率小,但是确实可能发生
    虽然我们讲的不是一回事:) 这里还是可以澄清你的一个疑问:
    1、如果你不在A里面把sqlCon置零的话,SqlCon的finalizer不会先执行,因为A保留了一个到sqlCon的引用。
    2、如果你自己在A里面把sqlCon置零的话,我想你不至于再故意调用sqlCon.Dispose()吧。
      

  4.   

    虽然我们讲的不是一回事:) 这里还是可以澄清你的一个疑问: 
    1、如果你不在A里面把sqlCon置零的话,SqlCon的finalizer不会先执行,因为A保留了一个到sqlCon的引用。 
    2、如果你自己在A里面把sqlCon置零的话,我想你不至于再故意调用sqlCon.Dispose()吧。 
    ///////////////////////
    我觉得你说得不对。  第1点里, 虽然A有sqlCon的引用,但是既然A已经在执行finalize了,说明A的引用已经为空了。 root都已经不可到达了,里面的子节点都是不可到达的(.NET框架程序设计中对finalize的解释中有提到)。请问你写过代码去测试吗?请看我的测试代码    class Base
        {
            ~Base()
            {
                Console.WriteLine("Base的finalize函数");
            }    }
        class MyClass
        {
            Base b = new Base();
            ~MyClass()
            {
                Console.WriteLine("MyClasss的finalize函数");
            }
        }
        class Program
        {        static void Main(string[] args)
            {
                MyClass m = new MyClass();
                m = null;
                GC.Collect();
                GC.WaitForPendingFinalizers();
                Console.Read();
            }
        }运行结果,确实是Base的Finalize函数先于MyClass的Finalize运行。
    当然sqlCon的 我们看不到它的Finalize运行情况,但是大家都一样是重写了Finalize的,情况应该想通吧。所以 我觉得你的观点不正确。
      

  5.   

    其实还有种情况: 
    A中包含sqlConnection对象sqlCon. 但是A的内存和sqlCon的内存分配在不同的代龄上,A在1代,sqlCon在0代。而当A不可到达时候的垃圾收集的时候,0代满了,1代没满,那么只会清理0代的不可到达对象。所以sqlCon的finalize被调用。第2次的时候,sqlCon的内存释放了之后,A的finalize才执行,而在A的finalize代码里,sqlCon.Dispose()已经不能访问。  为什么那么多例子里 都是直接就调用sqlCon的dispose方法,都没有考虑到sqlCon有可能已经是null了,为什么他们都不判断???? 这么多大师都没想到这个问题吗? 虽然这种概率小,但是确实可能发生
    ///////////////////////////////////////////////////////////////////////
    关于这个问题,我查阅到MSND的网络版,上面有段伪代码:
    http://msdn.microsoft.com/zh-cn/library/ms244737.aspx
    我提取一部分出来 (看起来像C#,但它说的是伪代码)protected virtual void Dispose(bool disposing)
        {
            if (disposing) 
            {
                // free managed resources
                if (managedResource != null)
                {
                    managedResource.Dispose();
                    managedResource = null;
                }
            }
            // free native resources if there are any.
            if (nativeResource != IntPtr.Zero) 
            {
                Marshal.FreeHGlobal(nativeResource);
                nativeResource = IntPtr.Zero;
            }
        }
    它这里居然是做了判断 if (managedResource != null)
    还有 if (nativeResource != IntPtr.Zero) 
    对于托管和非托管都做了判断。 但是我没找到c#代码,不知道是不是也是这样
    但是它的这个写法 ,至少肯定了我所说的, 这个IntPtr的Finalize可能已经先调用了,甚至连内存已经被回收了(我觉得出现内存都已经被回收了,就是因为代龄的原因)。 所以每个对象都先判断其引用是不是null才是真的正确的 。我一直困惑的就是为什么那么多书里都没有写出这点。 而我偶然看到这里的伪代码居然有写。 但是其他大师们却一直没注意这一点, 确实比较不解。 如果了解代龄的运作方式,要想到这个问题也不难。  请各位批评指教。。
      

  6.   

    我看了你在9楼的回复。你的测试的正确的,我的描述有错误。不过你要注意执行Finalizer和对象置零是两回事。在每一回合的垃圾回收中,为了让Finzalizer不会引用到已经销毁的对象,CLR在标志所有可回收对象后,把带有Finalizer的对象被放到一个队列中,并先执行该队列中对象的Finzlizer,然后才开始销毁对象。如果在执行Finalizer中出现了对其他可回收对象的引用,那么,该其他对象就被复活。由于Finalizer并不给引用置零,它也不直接销毁对象。因此回收垃圾并不会带来你担心的NullReference;而程序显式的置零带来的NullRefrence则属于编程逻辑错误。using System;
    class MyClass
    {
        Base b = new Base();
        ~MyClass()
        {
            Console.WriteLine("MyClasss的finalize函数");        Program.sBase = b;                    //<--复活Base
            GC.ReRegisterForFinalize(b);
        }
    }class Base
    {
        ~Base()
        {
            Console.WriteLine("Base的finalize函数");
        }}
    class Program
    {
        public static Base sBase;    static void Main(string[] args)
        {
            MyClass m = new MyClass();
            m = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Console.Read();        Program.sBase = null;
            GC.Collect();
            Console.Read();
        }
    }