呵呵,第一个问题有意思,没有这么做过。但有一点,不应该重写Finalize()函数。按照面向对象的原理来看,如果你重写了一个基类函数但没有使用override,它将隐藏基类中的函数,可能产生非预期的结果与冲突。所以第二个问题实际上应该是不考虑重写Finalize()方法前提下如何妥善使用Dispose()和析构函数(这是一个比较常见的问题了)。回答如下:
(1)首先要明白一个原则:资源在哪个类中被创建就应该在哪个类中清理。如果类中调用了其他基类中创建的资源,则应在基类中删除这些资源。然后看以下内容。
(2)析构函数:
析构函数是由垃圾回收器在清理对象时调用的。
因为.NET中的托管对象都是由垃圾回收器自动定期清理的,所以如果一个类中只有托管对象,则垃圾回收器在回收该对象时会同时一次性清理掉该类中创建的托管对象,此种情况下不要编写析构函数(情况A)。
如果一个类中创建使用了非托管资源(如数据库连接)(情况B),此时应该使用析构函数,但也只是作为忘记调用Dispose()函数的一种备份机制。换言之,此时,应该先掉用Dispose()函数来删除资源。
(3)Dispose()函数:
Dispose()函数由用户来调用。
在上面的情况A中,可以不调用Dispose()函数。但如果类中创建使用过一些较大的托管对象,最好尽快清除它们,此时可以在Dispose函数中删除它们,并由用户调用以尽快删除它们。在情况B中,应该在Dispose()中删除非托管资源,并由用户调用Dispose()。此时,为防止垃圾回收器再次调用析构函数,应该在Dispose()中调用GC.SuppressFinalize(this)通知垃圾回收器,此对象已经不再需要执行析构函数以免重复执行。但如果用户忘记了调用Dispose(),则垃圾回收器仍然会执行析构函数,保证非托管资源会被清除。以上对析构函数和Dispose()用法说明参看下面代码:public class MyClass():IDisposable
{
private StreamReader sr;
private int connection;
......
public void Dispose()
{
Dispose(true);
GC.SuppressFinally(this);
}
protected virtual void Dispose(bool disposing)
{
if(disposing)
{ //清理托管资源
if(sr!=null)
{
sr.Close();
sr=null;
}
}
//清理非托管对象
CloseConnection(); //假设类中有这样一个函数用于清理connection这个非托管资源
} ~MyClass()
{
Dispose(false);//仅仅清理非托管资源,除此外不应编写其它代码。
}
.....
}
(1)首先要明白一个原则:资源在哪个类中被创建就应该在哪个类中清理。如果类中调用了其他基类中创建的资源,则应在基类中删除这些资源。然后看以下内容。
(2)析构函数:
析构函数是由垃圾回收器在清理对象时调用的。
因为.NET中的托管对象都是由垃圾回收器自动定期清理的,所以如果一个类中只有托管对象,则垃圾回收器在回收该对象时会同时一次性清理掉该类中创建的托管对象,此种情况下不要编写析构函数(情况A)。
如果一个类中创建使用了非托管资源(如数据库连接)(情况B),此时应该使用析构函数,但也只是作为忘记调用Dispose()函数的一种备份机制。换言之,此时,应该先掉用Dispose()函数来删除资源。
(3)Dispose()函数:
Dispose()函数由用户来调用。
在上面的情况A中,可以不调用Dispose()函数。但如果类中创建使用过一些较大的托管对象,最好尽快清除它们,此时可以在Dispose函数中删除它们,并由用户调用以尽快删除它们。在情况B中,应该在Dispose()中删除非托管资源,并由用户调用Dispose()。此时,为防止垃圾回收器再次调用析构函数,应该在Dispose()中调用GC.SuppressFinalize(this)通知垃圾回收器,此对象已经不再需要执行析构函数以免重复执行。但如果用户忘记了调用Dispose(),则垃圾回收器仍然会执行析构函数,保证非托管资源会被清除。以上对析构函数和Dispose()用法说明参看下面代码:public class MyClass():IDisposable
{
private StreamReader sr;
private int connection;
......
public void Dispose()
{
Dispose(true);
GC.SuppressFinally(this);
}
protected virtual void Dispose(bool disposing)
{
if(disposing)
{ //清理托管资源
if(sr!=null)
{
sr.Close();
sr=null;
}
}
//清理非托管对象
CloseConnection(); //假设类中有这样一个函数用于清理connection这个非托管资源
} ~MyClass()
{
Dispose(false);//仅仅清理非托管资源,除此外不应编写其它代码。
}
.....
}
里面对你的问题又非常精确的回答
《Microsoft .NET框架程序设计》里的解释: http://www.china-pub.com/computers/ebook10000-15000/13534/CH19.zip
VB.net:“值类型的Finalize不会被调用?”的问题
http://blog.joycode.com/ninputer/archive/2005/01/12/42866.aspx C#:“C#中Finalize方法的问题”
http://blog.joycode.com/lijianzhong/archive/2005/01/13/43014.aspx 从SSCLI来分析上述问题
http://blog.joycode.com/lijianzhong/archive/2005/01/13/42991.aspx#FeedBack另外还有2个问题:
1.如果不写析构函数,编译器也应该会产生一个空的Finalize()吧,这个由析构函数隐式转换过来的Finalize()是由谁来调用的呢?2.按照rockrabbit(紫色石头)给的示例程序,如果在类中使用了非托管资源后,那么我首先写一个Dispose函数,手动的清理托管、非托管资源;然后,在析构函数中,仅仅清理非托管资源。
析构函数调用了Dispose(bool disposing),清理了非托管对象;Dispose()又是在什么时候执行的呢?
1.如果不写析构函数,编译器也应该会产生一个空的Finalize()吧,这个由析构函数隐式转换过来的Finalize()是由谁来调用的呢?
不会。Finalize是虚方法,但并不是纯虚(抽象)的,所以派生类不一定要写一个。
首先来看如下的代码:using System; public class Grandpapa
{
~Grandpapa(){ Console.WriteLine("Grandpapa.~Grandpapa");}
} public class Parent:Grandpapa
{
~Parent(){ Console.WriteLine("Parent.~Parent");}
} public class Son:Parent
{
~Son(){ Console.WriteLine("Son.~Son");}
} public class App
{
public static void Main()
{
Son s=new Son();
GC.Collect();
GC.WaitForPendingFinalizers();
}
} 这段代码的运行结果毫无疑问是: Son.~Son
Parent.~Parent
Grandpapa.~Grandpapa 这没什么问题。但是如果将Parent类重新定义如下,会出现什么情况呢? public class Parent:Grandpapa
{
protected void Finalize(){ Console.WriteLine("Parent.Finalize");}
}
运行结果变成了: Son.~Son
Parent.Finalize 情况已经有些不妙了,我在Parent中定义了一个“普通”的Finalize方法,竟然被它的子类Son的析构器给调用了?当然Finalize方法在C#中并不是一个“普通”的方法,析构器编译后就是一个有上述签名的Finalize方法。但C#编译器并没有禁止我们定义“普通”的Finalize,C#规范也没有指出定义这样的Finalize方法就是在定义一个析构器——实际上也不是,只是上述代码的表现如此——甚至还有这样一句诱人犯错的话:The compiler behaves as if this method(Finalize), and overrides of it, do not exist at all。分析IL代码可以看出,Parent中定义的“普通”的Finalize方法实际上“欺骗”了它的子类。它的子类只关心其父类是否定义了Finalize(当然签名要为上述形式)方法,它并不关心那个Finalize方法是否具有“析构器”语义。如果上述代码的行为通过理性分析还算可以接受的话,那么下面代码的运行结果就令人眩晕了,将Parent类重新定义如下(在上面的基础上添加了一个virtual关键字):public class Parent:Grandpapa
{
protected virtual void Finalize(){ Console.WriteLine("Parent.Finalize");}
} 编译后运行结果如下:Grandpapa.~Grandpapa这一次从IL代码的角度也解释不清了,我怀疑CLR对于析构器的判断是否还有另外的附加条件,但无论如何C#编译器呈现的行为是诡异的,因为这种结果放到哪里都是难以自圆其说的。我曾经为此挖掘了sscli源代码很长时间,但是就是找不到原因。
======================================================================我想了一会儿,觉得原因也许非常简单:所有的Finalize()方法都是这个样子:protected override Finalize()
{
//析构
base.Finalize();
}然后,最重要的是GC是这样调用Finalize():
((object) obj).Finalize()出现第一种情况是因为:
Parent覆盖了默认的Finalize(),但这对继承链没有造成任何影响!!Finalize在这里形成了两个分支:
protected override GrandPapa.Finnalize() {}// (A)
protected override Parent.Finalize() {}//这个方法不存在,但这并不会影响虚方法链。(B)
protected override Son.Finalize() { .... }//省略内容。(C)
另一条链:
protetced new Parent.Finalize() { Console.WriteLine("Parent.Finalize");}//new修饰符被上文作者省略了。(D)GC开始调用Finalize方法,因为ABC都是在object.Finalize()这个虚方法链上的,所以GC会先调用C方法。
然后,重要的分支出现了,因为D覆盖了B,所以C中的base.Finalize()是D方法!而D并未使用base.Finalize向上传播,所以执行到这里截止了。那么,第二种情况也很好解释了,第二种情况的继承链是:
protected override GrandPapa.Finnalize() {}// (A)
protected override Parent.Finalize() {}//不存在的方法(B)
另一条链:
protetced virtual Parent.Finalize() { Console.WriteLine("Parent.Finalize");}//这里同样省略了new修饰符。(D)
protected override Son.Finalize() { .... }//省略内容。(C)哈哈,现在知道为什么出现了上文作者百思不得其解的问题了吧,第二种情况由于Parent.Finalize有了virtual修饰,覆盖掉原来的override Parent.Finalize(),所以下面的override Son.Finalize()被继承到了virtual Parent.Finalize()下面。
结果导致GC先执行那个不存在的B方法,然后执行A方法。