C#中Finalize方法的问题
C#中Finalize方法的問題
ninputer在關于“值類型的Finalize不會被調用”中(http://blog.joycode.com/lijianzhong/archive/2005/01/13/42991.aspx#FeedBack)評論到“VB對Finalize管的可松呢,可以直接重寫、直接調用、允許不調用父類的Finalize,或者多次調用父類的Finalize等等…… 完全不像C#”。
其實C#的Finalize方法看起來只是比VB的好一點,但仍然有非常隱蔽的問題。問題如下。
首先來看如下的代碼:
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源代碼很長時間,但是就是找不到原因。
這一方面是C#編譯器的一個bug,另一方面也是CLR的一個bug。這個bug從.NET Framework的1.0版(VS.NET 2002),到1.1版(VS.NET 2003),以及Alpha版本的Longhorn操作系統中自帶的1.2版都存在。后來我寫信給C#的產品經理Eric Gunnerson(http://blogs.msdn.com/ericgu/)告訴他們這個bug。Eric Gunnerson隨后回信告訴我他們會修復這個bug。
我現在使用Visual C# Express 2005編譯器編譯(version 8.00.41013)上述代碼,后面兩種修改版都會得到一個warning:
warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
但是如果不理會這樣的警告,得到的exe文件執行行為仍然是非常奇怪。也就是說CLR中的bug仍然沒有fix。我個人認為對于C#編譯器來說,warning是不夠的,應該徹底禁止定義這樣的Finalize方法。
?
實際上在我的Effective .NET (in C#)一書的draft里也有這樣一個條款:
# 不要在一個類中有定義任何Finalize方法的念頭,因為那樣會對你的“析構器鏈”造成潛在的嚴重的傷害。
?
?
?
發表于 2005年1月13日 19:55
評論
?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徹底掩蓋掉,然后在其后續子類中就沒有原來那個Object的Finalize了!但如果CLR對Finalize特別干涉(就像Constructor那樣),Finalize的語義就會發生變化,VB的語法就要發生Break的變化,這是很可怕的。所以我們還是在編譯器上做這個限制比較安全。
?re: C#中Finalize方法的問題 2005-2-22 17:53
我想了一會兒,覺得原因也許非常簡單:
所有的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方法。。。。
?re: C#中Finalize方法的問題 2005-2-23 15:20
To Ivony,這個解釋比較牽強,而且很多地方都有漏洞:)
1。首先((object) obj).Finalize()這樣的說法就是錯誤的,對于虛方法來講:
obj.Finalize()和 ((object) obj).Finalize()的調用是完全一樣的,不存在任何區別。這本身就是多態應有之意。
2。“因為D覆蓋了B,所以C中的base.Finalize()是D方法”
這句話說得沒道理。實際上第一種情況的錯誤無非是C#將Finalize和~Parent()劃了等號,結果是定義
protected void Finalize(){ Console.WriteLine("Parent.Finalize");}
相當于定義了一個~Parent,只不過編譯器沒有在我們自己寫的Finalize中插入base.Finalize的調用。
3?!暗诙N情況由于Parent.Finalize有了virtual修飾,覆蓋掉原來的override Parent.Finalize()”
這個完全不叫覆蓋(override),而叫隱藏(hide),注意其中編譯后的元數據有一個newslot——也就是一個新的虛表slot。
4?!八韵旅娴膐verride Son.Finalize()被繼承到了virtual Parent.Finalize()下面?!?
抱歉,聽不懂這句話的意思。
5?!敖Y果導致GC先執行那個不存在的B方法”
不存在如何執行呢?
?re: C#中Finalize方法的問題 2005-2-23 17:49
1。首先((object) obj).Finalize()這樣的說法就是錯誤的,對于虛方法來講:
obj.Finalize()和 ((object) obj).Finalize()的調用是完全一樣的,不存在任何區別。這本身就是多態應有之意。
------------------------------------------
的確是這樣的,只需要:
object obj = xxxx;
然后obj.Finalize();就是執行object繼承鏈上的Finalize方法,寫一個強制類型轉換只不過是為了強調GC在調用Finalize方法的時候,是對一個object類型的對象調用的。
2。“因為D覆蓋了B,所以C中的base.Finalize()是D方法”
這句話說得沒道理。實際上第一種情況的錯誤無非是C#將Finalize和~Parent()劃了等號,結果是定義
protected void Finalize(){ Console.WriteLine("Parent.Finalize");}
相當于定義了一個~Parent,只不過編譯器沒有在我們自己寫的Finalize中插入base.Finalize的調用。
-----------------------------------------
不是這樣的,即使你寫了Finalize方法,默認的Finalize方法依然存在,這就是所謂的隱藏掉了基類的方法。然后Parent的子類在調用base.Finalize()的時候,就會調用你寫的那個,而不會導致原有默認的Finalize方法丟失。
3。“第二種情況由于Parent.Finalize有了virtual修飾,覆蓋掉原來的override Parent.Finalize()”
這個完全不叫覆蓋(override),而叫隱藏(hide),注意其中編譯后的元數據有一個newslot——也就是一個新的虛表slot。
----------------------------------------
嗯,微軟是叫隱藏,反正就是這個意思了。
4。“所以下面的override Son.Finalize()被繼承到了virtual Parent.Finalize()下面?!?
抱歉,聽不懂這句話的意思。
--------------------------------------
這里不知道怎么回事把我用于排版的空格給弄沒了,我再發一下繼承鏈(override鏈)。
第一種情況
protected override GrandPapa.Finnalize() {}//(A)
__protected override Parent.Finalize() {}//(B)雖然不存在,但仍是override鏈的一環
____protected override Son.Finalize() { .... }//(C)這個方法override掉了上面的(B),但其base.Finalize卻是下面的(D)。
protetced new Parent.Finalize() { Console.WriteLine("Parent.Finalize");}//(D),這個方法沒有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)
關鍵在這里:
class Son
{
__protected override Finalize()//這里是override掉了(D)而不是(B)。
}
5?!敖Y果導致GC先執行那個不存在的B方法”
不存在如何執行呢?
我們可以把情況想成這樣,當一個類沒有實現基類的virtual方法時,編譯器會變出一個默認的方法來維系這個override鏈(當然,只是假設):
[code]
override return_type Method( params )
{
return base.Methos( params );
}
[/code]
強調執行不存在的B方法只是為了說明override鏈。
方法雖然不存在,但在描述override鏈的時候,必須寫一下,否則override鏈就斷了。
?re: C#中Finalize方法的問題 2005-2-23 18:00
總結,當運行時多態的時候,系統會沿著override鏈條(上面的確是沒說清楚,不應該用繼承鏈這個詞)尋找鏈條終點上的方法執行。而并不是將這個對象還原成最原始的面目(Son)進行調用。
C#使用方法的簽名來判斷一個方法。
當出現上文中第一種情況時,由于兩個方法的簽名并不相同,一個是sealed的,一個是virtual的,所以override鏈并不會被破壞,系統找到了正確的方法進行執行。
然而在上文中第二種情況時,由于兩個方法的簽名完全一樣,都是virtual的,所以override鏈在這里被打斷,Son其實是override作者在Parant類中新定義的方法。
發現我的真的說不好,干脆待會兒我發幅圖片上來。。。
?re: C#中Finalize方法的問題 2005-2-23 18:10
1. 我理解你的意思,但是你的表述很容易讓人誤解。我想更準確的說法是:
GC調用的是一個類型中虛表slot上第一個Finalize方法。
只有在newslot——也就是一個類型中可能存在多個同名的虛表(new virtual)——時,才存在obj.Finalize()和 ((object) obj).Finalize()調用的區別。
2?!安皇沁@樣的,即使你寫了Finalize方法,默認的Finalize方法依然存在,這就是所謂的隱藏掉了基類的方法”
這不是隱藏(hide),而是實實在在的override——在同一個虛表slot上的override。
3。我們盡量用英文術語來討論,不然我很快就被你搞暈了,呵呵:)
4。我猜出你實際上在敘述虛表slot的問題,尤其是有了一個newslot。但是你的文字表述很難讓人看懂。
5。我們把“override鏈”還是說成虛表slot比較準確,也容易討論。
?re: C#中Finalize方法的問題 2005-2-23 18:38
還有一個地方,也就是第一種情況:
public class Parent:Grandpapa
{
protected void Finalize(){ Console.WriteLine("Parent.Finalize");}
}
這里沒有override關鍵字,所以應該不是override。。
我想寫一個程序來驗證我的想法。待會兒把結果發上來。
抱歉,因為我對于系統到底是怎么實現虛方法的了解太少了,只是憑感覺把自己的想法說了出來,所以很多地方都不是專業名詞,把大家給弄糊涂了。。。。
?re: C#中Finalize方法的問題 2005-2-23 18:38
To Ivony,
你的總結不錯,但我想更精煉、更準確的總結應該是這樣(可以在Rotor的源代碼中得到印證):
GC在調用Finalize方法時選擇的是第一個虛表slot上的Finalize方法(也就是Object當初定義的那個虛表slot上的Finalize方法)。
而第一種情況始終位于同一個虛表slot上。第二種情況則從class Parent開始引入了一個新的newslot。
很喜歡這樣的討論,歡迎常來:)
?re: C#中Finalize方法的問題 2005-2-23 18:47
To Ivony,
這里我錯了,確實不是override,雖然有的override并不需要override關鍵字。protected void Finalize()只是“欺騙”了C#編譯器,雖然它有一個警告。Thanks。
?re: C#中Finalize方法的問題 2005-2-23 19:08
將Son中的析構函數取掉后,驗證了我的想法,Parent.Finalize沒有被執行,可以肯定Parent.Finalize沒有override了。
?re: C#中Finalize方法的問題 2005-2-23 19:21
這應該是微軟的一個失誤,可能是設計人員將base.Finalize想當然的看成了被override掉的那個Finalize方法。而base.Method并不一定是你override掉的方法,這是C#里面的一個陷阱,也讓C#引入new修飾符后變得復雜。
有意思的是,在一個類里面不能同時寫Finalize和析構函數,編譯器會提示說已經存在了一個相同簽名的Finalize方法。哈哈。。。。
?re: C#中Finalize方法的問題 2005-2-25 13:46
再次出現古怪的現象:
public class Parent : Grandpapa , IFinalize
{
// ~Parent(){ Console.WriteLine("Parent.~Parent");}
#region IFinalize 成員
public void Finalize()
{
Console.WriteLine("Parent.IFinalize.Finalize");
}
#endregion
}
這段代碼的執行結果是拋出異常:
未處理的“System.TypeLoadException”類型的異常出現在 未知模塊 中。
其他信息: 方法實現中引用的聲明不能是 final 方法。類型:TestFinalize.Son,程序集:ConsoleApplication1, Version=1.0.1882.24453, Culture=neutral, PublicKeyToken=null。
?re: C#中Finalize方法的問題 2005-2-25 13:51
原因可能是在Son中會自動生成一個 override Finalize()方法,而編譯器認為這個Finalize方法是override Parent.IFinalize.Finalize的,所以又報錯。。。。但這發生在運行期。。。。
?re: C#中Finalize方法的問題 2005-2-28 13:09
IFinalize是一個什么接口呢?
?re: C#中Finalize方法的問題 2005-2-28 13:21
如果是一個自己定義的IFinalize接口:
interface IFinalize
{
void Finalize();
}
我并沒有遇到你所說的問題,能詳細講一下嗎?
?對不起,發得匆忙,代碼沒發全。 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的環境下測試的,2.0還沒有測試,因為據說2.0允許在子類中重新實現接口了。
運行結果:
未處理的“System.TypeLoadException”類型的異常出現在 未知模塊 中。
其他信息: 方法實現中引用的聲明不能是 final 方法。類型:TestFinalize.Son,程序集:ConsoleApplication1, Version=1.0.1886.32955, Culture=neutral, PublicKeyToken=null。
?re: C#中Finalize方法的問題 2005-3-2 12:09
將程序稍作改動,得到了另一個結果
public void Finalize()
改為:
public virtual void Finalize() //強令子類能夠覆蓋
結果:拋出異常。這一次是子類在重寫方法的時候不能降低訪問級別。。。。但奇怪的是,所有的這些編譯時就該出現的問題都是到了程序的運行期拋出異常。。。。
?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
我想您誤會了Anders 的意思了。在《C# Programming Language》的10.12 Destructors一節中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沒有明確說當hides ojbect這個Finalize方法的方法是個virtual的時候該怎么辦。
當沒有virtual這個modified notation的時候,Parent 中的Finalize方法hide了obejct的Finalize方法,但還有析構語意,所以在GC.Collect()還會被調用。
由于出現了一個new slot,Finalize在Parent又開辟了一個繼承鏈體系,跟ojbect的Finalize方法已經沒有任何關系了。Finalize方法在Parent中已經變成了沒有析構語意的普通方法,當然~son也就是overidding的也是這個沒有任何析構語意的Finalize方法,本身也就沒有了析構語意。既然沒有析構語意,當GC.Collect()的時候按照常理也不應該被調用的。
轉載于:https://www.cnblogs.com/cxd4321/archive/2006/10/19/533265.html
總結
以上是生活随笔為你收集整理的C#中Finalize方法的问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab字符串固定长度,限制Matl
- 下一篇: android AVB2.0(六)Sup