前段時(shí)間去鳥國出差,顛倒黑白,碌碌無為,疏于寫博,請大家理解。下面繼續(xù)前貼7月《C與C++社區(qū)混戰(zhàn),C#會重蹈覆轍嗎?》的討論。這次要談的是C#的析構(gòu)器的問題。這是C#中非常華而不實(shí)的一個設(shè)計(jì),不必要,且常常誤導(dǎo)很多C#er,且是.NET性能問題的常見陷阱地帶。下面逐項(xiàng)討論:
1.C#析構(gòu)器是一個丑陋的語法糖
C#析構(gòu)器(即Destructor)本質(zhì)上是對Finalize方法的一個override。既然是對Finalize方法的override,那就大大方方讓程序員去override 根類Object的Finalize方法好了?墒牵珻#設(shè)計(jì)師們首先搞了一個析構(gòu)器,接著又在編譯器里面把父類的Finalize方法隱藏掉(你去override的時(shí)候,告訴你父類沒有Finalize方法)。但是編譯完后,在IL代碼中又告訴你override了父類中的Finalize方法,而你寫的析構(gòu)器卻不翼而飛!
我在編程語言歷史上看到很多語法糖,有些語法糖華麗,有些語法糖冗贅。但是還從沒見過如此彎彎繞的語法糖!
2. C#析構(gòu)器偏離了析構(gòu)器原有的意思
析構(gòu)器自在各編程語言中造始,便有以下兩大基本含義:
(a) 回收對象內(nèi)部開銷的動態(tài)內(nèi)存以及各種資源
(b) 回收具有確定性時(shí)刻,比如delete對象時(shí),或者棧cleanup時(shí)。
可是C#將Finalize強(qiáng)扭成析構(gòu)器后,徹底丟失掉前面兩大基本含義,既無法回收動態(tài)內(nèi)存,又無法確定時(shí)刻調(diào)用(只能等GC在猴年馬月想起來才調(diào)用)。而只用于回收資源(而即便連這個任務(wù)也完成得很差,參見3.C#析構(gòu)器不能完成其設(shè)計(jì)的初衷)。這使得很多沿用以前析構(gòu)器概念的程序員經(jīng)常犯如下錯誤,比如:
class MyClass {
object field;
~MyClass() { field=null; } //既不必要,也嚴(yán)重?fù)p傷性能
}
class MyClass {
object field;
~MyClass() { GC.Collect(); } //既不必要,也嚴(yán)重、嚴(yán)重?fù)p傷性能
}
3. C#析構(gòu)器不能完成其設(shè)計(jì)的初衷
前面說過C#析構(gòu)器主要用于釋放對象的資源(非托管資源),而非內(nèi)存。
但很不幸,對于C#析構(gòu)器這個唯一的任務(wù),它卻不能很好地勝任。因?yàn)镃#析構(gòu)器(也就是Finalize方法)是由GC調(diào)用的,而GC只會在猴年馬月想起來才調(diào)用(回收對象之前的一輪回收),往往延誤了對象資源的釋放——而對象資源是非常昂貴的!∪绻娴倪@樣來做的話,項(xiàng)目會倒大霉——比如我們以前的一個項(xiàng)目,有部分程序員在析構(gòu)器中釋放一些native內(nèi)存,最后導(dǎo)致內(nèi)存暴漲——用戶抱怨下來,最后一調(diào)試發(fā)現(xiàn)原來都是在析構(gòu)器惹得禍——這些析構(gòu)器半天沒有被GC調(diào)用!
實(shí)際上,C#設(shè)計(jì)者在后來意識到這個問題了,于是又推出來一個Dispose方法(即Dispose模式)來讓用戶顯式釋放資源。然后又推薦程序員在Dispose里面GC.SuppressFinalize(). 即屏蔽析構(gòu)器!
既然Dispose能將事情(確定性地釋放非托管資源)做好,析構(gòu)器如此沒用,當(dāng)初設(shè)計(jì)它干嗎?這是再典型不過的多余設(shè)計(jì)了!
4. C#析構(gòu)器會帶來嚴(yán)重的性能障礙
a) C#析構(gòu)器會將對象的代標(biāo)記(Generation)拖大,使得對象更難以被GC回收,給GC造成更大性能負(fù)擔(dān)。
b) 析構(gòu)器本身釋放資源較晚,造成資源緊張,影響系統(tǒng)性能。
c) 析構(gòu)器執(zhí)行需要一個單獨(dú)的線程開銷,該線程的執(zhí)行(必須時(shí)間很短)需要其他線程停止,也是一個性能負(fù)擔(dān)。
這也是為什么C#推薦實(shí)現(xiàn)Dispose,不推薦實(shí)現(xiàn)析構(gòu)器的原因。因?yàn)槲鰳?gòu)器的性能代價(jià)太大?赡苤行№(xiàng)目的開發(fā)人員感受不到這一點(diǎn),但我相信做過大型項(xiàng)目的朋友,對C#析構(gòu)器的性能問題會有非常深的體會。
綜上,C#析構(gòu)器是C#設(shè)計(jì)師們純粹為了炫耀自己華麗語法糖、而不小心又失了手藝、一個拙劣的設(shè)計(jì)。
[ Update: ] 聽從網(wǎng)友的建議,把文章中“腦抽型、臭腳、sucks”等“罵街”的話刪除掉了。寫這些“罵街”的話實(shí)在是昨晚文章寫到深處,肝火旺盛,想到某些言論,情不自禁而已。并非我就是“潑婦”,今天一看自己昨晚的言論確實(shí)火力太猛,接受大家的意見,改正語言風(fēng)格,希望下面堅(jiān)持“技術(shù)討論不罵街“的原則。如果我有時(shí)候情不自禁做不到,希望大家監(jiān)督指點(diǎn),我會及時(shí)改過自新,重新做人:)