C#中Finalize方法的问题
C#中Finalize方法的問題
ninputer在關(guān)于“值類型的Finalize不會(huì)被調(diào)用”中(http://blog.joycode.com/lijianzhong/archive/2005/01/13/42991.aspx#FeedBack)評(píng)論到“VB對Finalize管的可松呢,可以直接重寫、直接調(diào)用、允許不調(diào)用父類的Finalize,或者多次調(diào)用父類的Finalize等等…… 完全不像C#”。
其實(shí)C#的Finalize方法看起來只是比VB的好一點(diǎn),但仍然有非常隱蔽的問題。問題如下。
首先來看如下的代碼:
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();
???? }
}
這段代碼的運(yùn)行結(jié)果毫無疑問是:
Son.~Son
Parent.~Parent
Grandpapa.~Grandpapa
這沒什么問題。但是如果將Parent類重新定義如下,會(huì)出現(xiàn)什么情況呢?
public class Parent:Grandpapa
{
???? protected void Finalize(){ Console.WriteLine("Parent.Finalize");}
}
運(yùn)行結(jié)果變成了:
Son.~Son
Parent.Finalize
情況已經(jīng)有些不妙了,我在Parent中定義了一個(gè)“普通”的Finalize方法,竟然被它的子類Son的析構(gòu)器給調(diào)用了?
當(dāng)然Finalize方法在C#中并不是一個(gè)“普通”的方法,析構(gòu)器編譯后就是一個(gè)有上述簽名的Finalize方法。但C#編譯器并沒有禁止我們定義“普通”的Finalize,
C#規(guī)范也沒有指出定義這樣的Finalize方法就是在定義一個(gè)析構(gòu)器——實(shí)際上也不是,只是上述代碼的表現(xiàn)如此——甚至還有這樣一句誘人犯錯(cuò)的話:The compiler behaves as if this method(Finalize), and overrides of it, do not exist at all。分析IL代碼可以看出,Parent中定義的“普通”的Finalize方法實(shí)際上“欺騙”了它的子類。它的子類只關(guān)心其父類是否定義了Finalize(當(dāng)然簽名要為上述形式)方法,它并不關(guān)心那個(gè)Finalize方法是否具有“析構(gòu)器”語義。
如果上述代碼的行為通過理性分析還算可以接受的話,那么下面代碼的運(yùn)行結(jié)果就令人眩暈了,將Parent類重新定義如下(在上面的基礎(chǔ)上添加了一個(gè)virtual關(guān)鍵字):
public class Parent:Grandpapa
{
???? protected virtual void Finalize(){ Console.WriteLine("Parent.Finalize");}
}
編譯后運(yùn)行結(jié)果如下:
Grandpapa.~Grandpapa
這一次從IL代碼的角度也解釋不清了,我懷疑CLR對于析構(gòu)器的判斷是否還有另外的附加條件,但無論如何C#編譯器呈現(xiàn)的行為是詭異的,因?yàn)檫@種結(jié)果放到哪里都是難以自圓其說的。我曾經(jīng)為此挖掘了sscli源代碼很長時(shí)間,但是就是找不到原因。
這一方面是C#編譯器的一個(gè)bug,另一方面也是CLR的一個(gè)bug。這個(gè)bug從.NET Framework的1.0版(VS.NET 2002),到1.1版(VS.NET 2003),以及Alpha版本的Longhorn操作系統(tǒng)中自帶的1.2版都存在。后來我寫信給C#的產(chǎn)品經(jīng)理Eric Gunnerson(http://blogs.msdn.com/ericgu/)告訴他們這個(gè)bug。Eric Gunnerson隨后回信告訴我他們會(huì)修復(fù)這個(gè)bug。
我現(xiàn)在使用Visual C# Express 2005編譯器編譯(version 8.00.41013)上述代碼,后面兩種修改版都會(huì)得到一個(gè)warning:
warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
但是如果不理會(huì)這樣的警告,得到的exe文件執(zhí)行行為仍然是非常奇怪。也就是說CLR中的bug仍然沒有fix。我個(gè)人認(rèn)為對于C#編譯器來說,warning是不夠的,應(yīng)該徹底禁止定義這樣的Finalize方法。
?
實(shí)際上在我的Effective .NET (in C#)一書的draft里也有這樣一個(gè)條款:
# 不要在一個(gè)類中有定義任何Finalize方法的念頭,因?yàn)槟菢訒?huì)對你的“析構(gòu)器鏈”造成潛在的嚴(yán)重的傷害。
?
?
?
發(fā)表于 2005年1月13日 19:55
評(píng)論
?re: C#中Finalize方法的問題 2005-1-13 21:06
受救了。
?re: C#中Finalize方法的問題 2005-1-13 21:06
受教了。
?re: C#中Finalize方法的問題 2005-1-13 21:20
我喜歡這樣的交流:)
?re: C#中Finalize方法的問題 2005-1-14 1:03
贊
?re: C#中Finalize方法的問題 2005-1-14 9:08
好啊,看來我拋的磚頭引來了不少美玉啊,呵呵
看來該有人寫寫VB的書說說這些情況,好讓VB程序員用好.NET語言中唯一可以自由使用的Finalize。
?re: C#中Finalize方法的問題 2005-1-14 9:13
在VB里更可以用Shadows將基類的Finalize徹底掩蓋掉,然后在其后續(xù)子類中就沒有原來那個(gè)Object的Finalize了!但如果CLR對Finalize特別干涉(就像Constructor那樣),Finalize的語義就會(huì)發(fā)生變化,VB的語法就要發(fā)生Break的變化,這是很可怕的。所以我們還是在編譯器上做這個(gè)限制比較安全。
?re: C#中Finalize方法的問題 2005-2-22 17:53
我想了一會(huì)兒,覺得原因也許非常簡單:
所有的Finalize()方法都是這個(gè)樣子:
protected override Finalize()
{
//析構(gòu)
base.Finalize();
}
然后,最重要的是GC是這樣調(diào)用Finalize():
((object) obj).Finalize()
出現(xiàn)第一種情況是因?yàn)?#xff1a;
Parent覆蓋了默認(rèn)的Finalize(),但這對繼承鏈沒有造成任何影響!!Finalize在這里形成了兩個(gè)分支:
protected override GrandPapa.Finnalize() {}// (A)
protected override Parent.Finalize() {}//這個(gè)方法不存在,但這并不會(huì)影響虛方法鏈。(B)
protected override Son.Finalize() { .... }//省略內(nèi)容。(C)
protetced new Parent.Finalize() { Console.WriteLine("Parent.Finalize");}//new修飾符被作者省略了。(D)
GC開始調(diào)用Finalize方法,因?yàn)锳BC都是在object.Finalize()這個(gè)虛方法鏈上的,所以GC會(huì)先調(diào)用C方法。
然后,重要的分支出現(xiàn)了,因?yàn)镈覆蓋了B,所以C中的base.Finalize()是D方法!而D并未使用base.Finalize向上傳播,所以執(zhí)行到這里截止了。
那么,第二種情況也很好解釋了,第二種情況的繼承鏈?zhǔn)?#xff1a;
protected override GrandPapa.Finnalize() {}// (A)
protected override Parent.Finalize() {}//不存在的方法(B)
protetced virtual Parent.Finalize() { Console.WriteLine("Parent.Finalize");}//這里同樣省略了new修飾符。(D)
protected override Son.Finalize() { .... }//省略內(nèi)容。(C)
哈哈,現(xiàn)在知道為什么出現(xiàn)了作者百思不得其解的問題了吧,第二種情況由于Parent.Finalize有了virtual修飾,覆蓋掉原來的override Parent.Finalize(),所以下面的override Son.Finalize()被繼承到了virtual Parent.Finalize()下面。
結(jié)果導(dǎo)致GC先執(zhí)行那個(gè)不存在的B方法,然后執(zhí)行A方法。。。。
?re: C#中Finalize方法的問題 2005-2-23 15:20
To Ivony,這個(gè)解釋比較牽強(qiáng),而且很多地方都有漏洞:)
1。首先((object) obj).Finalize()這樣的說法就是錯(cuò)誤的,對于虛方法來講:
obj.Finalize()和 ((object) obj).Finalize()的調(diào)用是完全一樣的,不存在任何區(qū)別。這本身就是多態(tài)應(yīng)有之意。
2。“因?yàn)镈覆蓋了B,所以C中的base.Finalize()是D方法”
這句話說得沒道理。實(shí)際上第一種情況的錯(cuò)誤無非是C#將Finalize和~Parent()劃了等號(hào),結(jié)果是定義
protected void Finalize(){ Console.WriteLine("Parent.Finalize");}
相當(dāng)于定義了一個(gè)~Parent,只不過編譯器沒有在我們自己寫的Finalize中插入base.Finalize的調(diào)用。
3。“第二種情況由于Parent.Finalize有了virtual修飾,覆蓋掉原來的override Parent.Finalize()”
這個(gè)完全不叫覆蓋(override),而叫隱藏(hide),注意其中編譯后的元數(shù)據(jù)有一個(gè)newslot——也就是一個(gè)新的虛表slot。
4。“所以下面的override Son.Finalize()被繼承到了virtual Parent.Finalize()下面。”
抱歉,聽不懂這句話的意思。
5。“結(jié)果導(dǎo)致GC先執(zhí)行那個(gè)不存在的B方法”
不存在如何執(zhí)行呢?
?re: C#中Finalize方法的問題 2005-2-23 17:49
1。首先((object) obj).Finalize()這樣的說法就是錯(cuò)誤的,對于虛方法來講:
obj.Finalize()和 ((object) obj).Finalize()的調(diào)用是完全一樣的,不存在任何區(qū)別。這本身就是多態(tài)應(yīng)有之意。
------------------------------------------
的確是這樣的,只需要:
object obj = xxxx;
然后obj.Finalize();就是執(zhí)行object繼承鏈上的Finalize方法,寫一個(gè)強(qiáng)制類型轉(zhuǎn)換只不過是為了強(qiáng)調(diào)GC在調(diào)用Finalize方法的時(shí)候,是對一個(gè)object類型的對象調(diào)用的。
2。“因?yàn)镈覆蓋了B,所以C中的base.Finalize()是D方法”
這句話說得沒道理。實(shí)際上第一種情況的錯(cuò)誤無非是C#將Finalize和~Parent()劃了等號(hào),結(jié)果是定義
protected void Finalize(){ Console.WriteLine("Parent.Finalize");}
相當(dāng)于定義了一個(gè)~Parent,只不過編譯器沒有在我們自己寫的Finalize中插入base.Finalize的調(diào)用。
-----------------------------------------
不是這樣的,即使你寫了Finalize方法,默認(rèn)的Finalize方法依然存在,這就是所謂的隱藏掉了基類的方法。然后Parent的子類在調(diào)用base.Finalize()的時(shí)候,就會(huì)調(diào)用你寫的那個(gè),而不會(huì)導(dǎo)致原有默認(rèn)的Finalize方法丟失。
3。“第二種情況由于Parent.Finalize有了virtual修飾,覆蓋掉原來的override Parent.Finalize()”
這個(gè)完全不叫覆蓋(override),而叫隱藏(hide),注意其中編譯后的元數(shù)據(jù)有一個(gè)newslot——也就是一個(gè)新的虛表slot。
----------------------------------------
嗯,微軟是叫隱藏,反正就是這個(gè)意思了。
4。“所以下面的override Son.Finalize()被繼承到了virtual Parent.Finalize()下面。”
抱歉,聽不懂這句話的意思。
--------------------------------------
這里不知道怎么回事把我用于排版的空格給弄沒了,我再發(fā)一下繼承鏈(override鏈)。
第一種情況
protected override GrandPapa.Finnalize() {}//(A)
__protected override Parent.Finalize() {}//(B)雖然不存在,但仍是override鏈的一環(huán)
____protected override Son.Finalize() { .... }//(C)這個(gè)方法override掉了上面的(B),但其base.Finalize卻是下面的(D)。
protetced new Parent.Finalize() { Console.WriteLine("Parent.Finalize");}//(D),這個(gè)方法沒有override任何方法,不在override鏈中。
第二種情況
protected override GrandPapa.Finnalize() {}// (A)
__protected override Parent.Finalize() {}//(B)
protetced virtual Parent.Finalize() { Console.WriteLine("Parent.Finalize");}//(D)
__protected override Son.Finalize() { .... }//(C)
關(guān)鍵在這里:
class Son
{
__protected override Finalize()//這里是override掉了(D)而不是(B)。
}
5。“結(jié)果導(dǎo)致GC先執(zhí)行那個(gè)不存在的B方法”
不存在如何執(zhí)行呢?
我們可以把情況想成這樣,當(dāng)一個(gè)類沒有實(shí)現(xiàn)基類的virtual方法時(shí),編譯器會(huì)變出一個(gè)默認(rèn)的方法來維系這個(gè)override鏈(當(dāng)然,只是假設(shè)):
[code]
override return_type Method( params )
{
return base.Methos( params );
}
[/code]
強(qiáng)調(diào)執(zhí)行不存在的B方法只是為了說明override鏈。
方法雖然不存在,但在描述override鏈的時(shí)候,必須寫一下,否則override鏈就斷了。
?re: C#中Finalize方法的問題 2005-2-23 18:00
總結(jié),當(dāng)運(yùn)行時(shí)多態(tài)的時(shí)候,系統(tǒng)會(huì)沿著override鏈條(上面的確是沒說清楚,不應(yīng)該用繼承鏈這個(gè)詞)尋找鏈條終點(diǎn)上的方法執(zhí)行。而并不是將這個(gè)對象還原成最原始的面目(Son)進(jìn)行調(diào)用。
C#使用方法的簽名來判斷一個(gè)方法。
當(dāng)出現(xiàn)上文中第一種情況時(shí),由于兩個(gè)方法的簽名并不相同,一個(gè)是sealed的,一個(gè)是virtual的,所以override鏈并不會(huì)被破壞,系統(tǒng)找到了正確的方法進(jìn)行執(zhí)行。
然而在上文中第二種情況時(shí),由于兩個(gè)方法的簽名完全一樣,都是virtual的,所以override鏈在這里被打斷,Son其實(shí)是override作者在Parant類中新定義的方法。
發(fā)現(xiàn)我的真的說不好,干脆待會(huì)兒我發(fā)幅圖片上來。。。
?re: C#中Finalize方法的問題 2005-2-23 18:10
1. 我理解你的意思,但是你的表述很容易讓人誤解。我想更準(zhǔn)確的說法是:
GC調(diào)用的是一個(gè)類型中虛表slot上第一個(gè)Finalize方法。
只有在newslot——也就是一個(gè)類型中可能存在多個(gè)同名的虛表(new virtual)——時(shí),才存在obj.Finalize()和 ((object) obj).Finalize()調(diào)用的區(qū)別。
2。“不是這樣的,即使你寫了Finalize方法,默認(rèn)的Finalize方法依然存在,這就是所謂的隱藏掉了基類的方法”
這不是隱藏(hide),而是實(shí)實(shí)在在的override——在同一個(gè)虛表slot上的override。
3。我們盡量用英文術(shù)語來討論,不然我很快就被你搞暈了,呵呵:)
4。我猜出你實(shí)際上在敘述虛表slot的問題,尤其是有了一個(gè)newslot。但是你的文字表述很難讓人看懂。
5。我們把“override鏈”還是說成虛表slot比較準(zhǔn)確,也容易討論。
?re: C#中Finalize方法的問題 2005-2-23 18:38
還有一個(gè)地方,也就是第一種情況:
public class Parent:Grandpapa
{
protected void Finalize(){ Console.WriteLine("Parent.Finalize");}
}
這里沒有override關(guān)鍵字,所以應(yīng)該不是override。。
我想寫一個(gè)程序來驗(yàn)證我的想法。待會(huì)兒把結(jié)果發(fā)上來。
抱歉,因?yàn)槲覍τ谙到y(tǒng)到底是怎么實(shí)現(xiàn)虛方法的了解太少了,只是憑感覺把自己的想法說了出來,所以很多地方都不是專業(yè)名詞,把大家給弄糊涂了。。。。
?re: C#中Finalize方法的問題 2005-2-23 18:38
To Ivony,
你的總結(jié)不錯(cuò),但我想更精煉、更準(zhǔn)確的總結(jié)應(yīng)該是這樣(可以在Rotor的源代碼中得到印證):
GC在調(diào)用Finalize方法時(shí)選擇的是第一個(gè)虛表slot上的Finalize方法(也就是Object當(dāng)初定義的那個(gè)虛表slot上的Finalize方法)。
而第一種情況始終位于同一個(gè)虛表slot上。第二種情況則從class Parent開始引入了一個(gè)新的newslot。
很喜歡這樣的討論,歡迎常來:)
?re: C#中Finalize方法的問題 2005-2-23 18:47
To Ivony,
這里我錯(cuò)了,確實(shí)不是override,雖然有的override并不需要override關(guān)鍵字。protected void Finalize()只是“欺騙”了C#編譯器,雖然它有一個(gè)警告。Thanks。
?re: C#中Finalize方法的問題 2005-2-23 19:08
將Son中的析構(gòu)函數(shù)取掉后,驗(yàn)證了我的想法,Parent.Finalize沒有被執(zhí)行,可以肯定Parent.Finalize沒有override了。
?re: C#中Finalize方法的問題 2005-2-23 19:21
這應(yīng)該是微軟的一個(gè)失誤,可能是設(shè)計(jì)人員將base.Finalize想當(dāng)然的看成了被override掉的那個(gè)Finalize方法。而base.Method并不一定是你override掉的方法,這是C#里面的一個(gè)陷阱,也讓C#引入new修飾符后變得復(fù)雜。
有意思的是,在一個(gè)類里面不能同時(shí)寫Finalize和析構(gòu)函數(shù),編譯器會(huì)提示說已經(jīng)存在了一個(gè)相同簽名的Finalize方法。哈哈。。。。
?re: C#中Finalize方法的問題 2005-2-25 13:46
再次出現(xiàn)古怪的現(xiàn)象:
public class Parent : Grandpapa , IFinalize
{
// ~Parent(){ Console.WriteLine("Parent.~Parent");}
#region IFinalize 成員
public void Finalize()
{
Console.WriteLine("Parent.IFinalize.Finalize");
}
#endregion
}
這段代碼的執(zhí)行結(jié)果是拋出異常:
未處理的“System.TypeLoadException”類型的異常出現(xiàn)在 未知模塊 中。
其他信息: 方法實(shí)現(xiàn)中引用的聲明不能是 final 方法。類型:TestFinalize.Son,程序集:ConsoleApplication1, Version=1.0.1882.24453, Culture=neutral, PublicKeyToken=null。
?re: C#中Finalize方法的問題 2005-2-25 13:51
原因可能是在Son中會(huì)自動(dòng)生成一個(gè) override Finalize()方法,而編譯器認(rèn)為這個(gè)Finalize方法是override Parent.IFinalize.Finalize的,所以又報(bào)錯(cuò)。。。。但這發(fā)生在運(yùn)行期。。。。
?re: C#中Finalize方法的問題 2005-2-28 13:09
IFinalize是一個(gè)什么接口呢?
?re: C#中Finalize方法的問題 2005-2-28 13:21
如果是一個(gè)自己定義的IFinalize接口:
interface IFinalize
{
void Finalize();
}
我并沒有遇到你所說的問題,能詳細(xì)講一下嗎?
?對不起,發(fā)得匆忙,代碼沒發(fā)全。 2005-3-1 18:29
using System;
namespace TestFinalize
{
public class Parent: IFinalize
{
public void Finalize()
{
Console.WriteLine("Parent.IFinalize.Finalize");
}
}
public class Son : Parent
{
~Son(){ Console.WriteLine("Son.~Son");}
}
public interface IFinalize
{
void Finalize();
}
public class App
{
[STAThread]
public static void Main()
{
Son s=new Son();
s = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.ReadLine();
}
}
}
順便說一下,我是在.NET Framework 1.1的環(huán)境下測試的,2.0還沒有測試,因?yàn)閾?jù)說2.0允許在子類中重新實(shí)現(xiàn)接口了。
運(yùn)行結(jié)果:
未處理的“System.TypeLoadException”類型的異常出現(xiàn)在 未知模塊 中。
其他信息: 方法實(shí)現(xiàn)中引用的聲明不能是 final 方法。類型:TestFinalize.Son,程序集:ConsoleApplication1, Version=1.0.1886.32955, Culture=neutral, PublicKeyToken=null。
?re: C#中Finalize方法的問題 2005-3-2 12:09
將程序稍作改動(dòng),得到了另一個(gè)結(jié)果
public void Finalize()
改為:
public virtual void Finalize() //強(qiáng)令子類能夠覆蓋
結(jié)果:拋出異常。這一次是子類在重寫方法的時(shí)候不能降低訪問級(jí)別。。。。但奇怪的是,所有的這些編譯時(shí)就該出現(xiàn)的問題都是到了程序的運(yùn)行期拋出異常。。。。
?re:C#中Finalize方法的問題 2005-4-10 20:28
^_^,Pretty Good!
?re:C#中Finalize方法的問題 2005-4-16 17:17
^_~
?re:C#中Finalize方法的問題 2005-7-17 11:40
C#中Finalize方法的問題ooeess
?re:C#中Finalize方法的問題 2005-8-1 18:07
C#中Finalize方法的問題ooeess
?re: C#中Finalize方法的問題 2005-11-13 16:08
我想您誤會(huì)了Anders 的意思了。在《C# Programming Language》的10.12 Destructors一節(jié)中Anders是這樣說的
Destructors are implemented by overriding the virtual method Finalize on System.Object. C# programs are not permitted to override this method or call it (or overrides of it) directly. For instance, the program
class A
{
override protected void Finalize() {} // error
public void F() {
this.Finalize(); // error
}
}
contains two errors.
The compiler behaves as if this method, and overrides of it, do not exist at all。Thus, this program
class A
{
void Finalize() {} // permitted
}
is valid, and the method shown hides System.Object's Finalize method.
從前后文來看,Anders所指的as if the method中的method特指的是object的Finalize方法。而不是您自己所定義的Finalize方法,而Anders后面所舉的例子也正如您說的,自己定義的Finalize方法,hide了System.Object's Finalize方法。
?re: C#中Finalize方法的問題 2005-11-13 17:01
只是Anders沒有明確說當(dāng)hides ojbect這個(gè)Finalize方法的方法是個(gè)virtual的時(shí)候該怎么辦。
當(dāng)沒有virtual這個(gè)modified notation的時(shí)候,Parent 中的Finalize方法hide了obejct的Finalize方法,但還有析構(gòu)語意,所以在GC.Collect()還會(huì)被調(diào)用。
由于出現(xiàn)了一個(gè)new slot,Finalize在Parent又開辟了一個(gè)繼承鏈體系,跟ojbect的Finalize方法已經(jīng)沒有任何關(guān)系了。Finalize方法在Parent中已經(jīng)變成了沒有析構(gòu)語意的普通方法,當(dāng)然~son也就是overidding的也是這個(gè)沒有任何析構(gòu)語意的Finalize方法,本身也就沒有了析構(gòu)語意。既然沒有析構(gòu)語意,當(dāng)GC.Collect()的時(shí)候按照常理也不應(yīng)該被調(diào)用的。
轉(zhuǎn)載于:https://www.cnblogs.com/cxd4321/archive/2006/10/19/533265.html
總結(jié)
以上是生活随笔為你收集整理的C#中Finalize方法的问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab字符串固定长度,限制Matl
- 下一篇: android AVB2.0(六)Sup