大家知道,托管代碼一個(gè)重要的特點(diǎn)是自動(dòng)管理內(nèi)存,即我們常說(shuō)的垃圾回收機(jī)制,那些高大上的理論我就不重復(fù)了,有興趣的朋友可以翻書(shū)。我這個(gè)有個(gè)毛病——不喜歡很嚴(yán)肅地去說(shuō)一些理論的東西,所以我不多介紹了。
一般而言,當(dāng)代碼執(zhí)行超出某個(gè)變量的有效范圍后,或者不再引用某個(gè)對(duì)象實(shí)例時(shí),該實(shí)例會(huì)發(fā)生析構(gòu),垃圾回收器很可能就要清理門戶了,當(dāng)然也可能不是馬上清理,也許會(huì)過(guò)一會(huì)兒再清理。
對(duì)于一些要自定義進(jìn)行清理操作的類,我們會(huì)采取以下方案:
1、寫(xiě)上析構(gòu)函數(shù),在析構(gòu)函數(shù)中清理。
2、實(shí)現(xiàn)IDisposable接口,并實(shí)現(xiàn)Dispose方法,在方法中編寫(xiě)自定義清理代碼。當(dāng)該類型被實(shí)例化后,最后不再使用時(shí)會(huì)調(diào)用Dispose方法清理,如果順利清理,最后還會(huì)調(diào)用類型的析構(gòu)函數(shù)。通常,如何實(shí)現(xiàn)了IDisposable接口,就不必再寫(xiě)上析構(gòu)函數(shù)了。如果希望Dispose方法被自動(dòng)調(diào)用,可以在實(shí)例化對(duì)象的代碼包裝在using語(yǔ)句塊中,當(dāng)執(zhí)行完using塊時(shí)會(huì)自動(dòng)調(diào)用Dispose方法。
可能有人笑了,老周,你太逗了,這些基礎(chǔ)知識(shí)誰(shuí)不知道?當(dāng)然,我說(shuō)上面那些內(nèi)容是為了繞個(gè)小圈子,以便進(jìn)入主題。于是,我產(chǎn)生了一個(gè)疑問(wèn):是不是存在某些情景下,可能導(dǎo)致對(duì)象實(shí)例不會(huì)被回收呢?就算你調(diào)用了Dispose方法,就算你把變量設(shè)為null來(lái)解除引用,就算你調(diào)用GC類的方法來(lái)回收……
經(jīng)過(guò)老周測(cè)試,還真有這種情況,而且很多朋友都很有可能會(huì)忽略,甚至在意識(shí)認(rèn)知上誤認(rèn)為對(duì)象實(shí)例已經(jīng)被回收,而實(shí)際上是沒(méi)有回收的。
我簡(jiǎn)單說(shuō)一下這種情形:
比如有一個(gè)靜態(tài)類(靜態(tài)類的成員必是靜態(tài)的)A,里面有靜態(tài)事件。隨后在其他類的實(shí)例中處理A類的靜態(tài)事件,并且處理事件的方法就位于這個(gè)實(shí)例對(duì)象上……
不急,我們還是看真實(shí)的例子吧。假如我定義了一個(gè)靜態(tài)類MyChecker,它里面有個(gè)靜態(tài)事件CheckEvent。
public static class MyChecker { #region 靜態(tài)事件 public static event EventHandler CheckEvent; #endregion public static void CallEvent() { if (CheckEvent != null) { CheckEvent(new object(), EventArgs.Empty); } } }
只要CallEvent方法被調(diào)用,CheckEvent事件會(huì)被引發(fā)。
然后,定義另一個(gè)類SampleClass,并在該類中處理剛才MyChecker中的靜態(tài)事件。
class SampleClass:IDisposable { public SampleClass() { MyChecker.CheckEvent += MyChecker_CheckEvent; } void MyChecker_CheckEvent(object sender, EventArgs e) { new Form2().Show(); } ~SampleClass() { System.Diagnostics.Debug.WriteLine("/n看,析構(gòu)函數(shù)調(diào)用了。/n"); } public void Dispose() { //…… } }
在類的構(gòu)造函數(shù)中,附加CheckEvent事件的處理,處理方法名為MyChecker_CheckEvent。
可能大家已經(jīng)發(fā)現(xiàn),老周寫(xiě)的SampleClass類有點(diǎn)恐怖氣息,既實(shí)現(xiàn)了Dispose方法,怎么又寫(xiě)了析構(gòu)函數(shù),我這里寫(xiě)上析構(gòu)函數(shù)是為了驗(yàn)證類的實(shí)例是否真的被清理,如果實(shí)例真的被回收,那么Debug類會(huì)在“輸出”窗口中輸出提示,如果沒(méi)有提示輸出,說(shuō)明類的實(shí)例還霸占著內(nèi)存。
接下來(lái)測(cè)試一下。
SampleClass sc = new SampleClass(); await Task.Delay(10 * 1000); sc.Dispose(); sc = null; GC.Collect();
實(shí)例化SampleClass后,然后Delay會(huì)暫停10秒,10秒鐘過(guò)后會(huì)調(diào)用Dispose方法,并設(shè)置變量為null引用,我害怕不能及時(shí)清理,連GC.Collect方法也用上了。
而在等待這10秒期間,可以調(diào)用靜態(tài)類的CallEvent方法來(lái)引發(fā)靜態(tài)事件CheckEvent。
MyChecker.CallEvent();
按照一般理解,在10秒鐘后,SampleClass實(shí)例應(yīng)該被清理,并且在“輸出”窗口會(huì)輸出提示。
好,現(xiàn)在試一下。
……
實(shí)驗(yàn)結(jié)果表明,輸出 窗口中連鴨毛都沒(méi)有輸出,這說(shuō)明10秒鐘后,SampleClass實(shí)例根本沒(méi)有發(fā)生析構(gòu)。于是又出問(wèn)題了,這是怎么回事?SampleClass實(shí)例不是不存在引用了嗎,怎么不發(fā)生析構(gòu)?
其實(shí)我們忽略了一點(diǎn):靜態(tài)事件CheckEvent還跟SampleClass實(shí)例的方法綁定著呢,實(shí)質(zhì)上,雖然將變量設(shè)為null,可是SampleClass實(shí)例中的MyChecker_CheckEvent方法還被靜態(tài)類中的靜態(tài)事件引用著,所以不會(huì)被回收。不知道你明白了沒(méi)。
這個(gè)問(wèn)題很多朋友在實(shí)際開(kāi)發(fā)中都會(huì)忽略,還得意地以為Sample實(shí)例真的被回收了,實(shí)際上實(shí)例不會(huì)被回收,除非程序結(jié)束。因?yàn)镸yChecker是靜態(tài)類,不基于實(shí)例。如果MyChecker不是靜態(tài)類,那么當(dāng)MyChecker的實(shí)例釋放后,SampleClass實(shí)例就可以被釋放了。
那么,如何解決呢?很簡(jiǎn)單,只要在SampleClass類的Dispose方法中解除靜態(tài)事件與方法的綁定即可,這樣的話,靜態(tài)事件就不再引用實(shí)例中的方法成員了,此時(shí)實(shí)例就可以發(fā)生析構(gòu)了。
public void Dispose() { MyChecker.CheckEvent -= MyChecker_CheckEvent; }
這個(gè)例子研究,告訴我們:在類實(shí)例中處理靜態(tài)事件時(shí)一定要小心。
本示例的源碼下載:http://files.VEVb.com/files/tcjiaan/refsample.zip
好了,今天就扯到這里吧。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注