一、背景

          最近在精讀 《CLR Via C#》和 《Effective C#》 的時(shí)候,發(fā)現(xiàn)的一個(gè)問題點(diǎn)。一般來說,我們實(shí)現(xiàn) IDisposable
          接口,是為了釋放托管資源和非托管資源。不過在 C# 類型定義里面有一個(gè)功能類似的東西,那就是終結(jié)器。

          最開始我是學(xué) C++ 的,之后學(xué) C# 的時(shí)候發(fā)現(xiàn)這玩意兒不論是寫法和作用,都跟 C++ 里面的 析構(gòu)函數(shù) 一樣。在 C++
          里面的析構(gòu)函數(shù)是在對(duì)象釋放的時(shí)候會(huì)被調(diào)用,之后這個(gè)觀點(diǎn)一直被我?guī)У?C#,認(rèn)為資源釋放的動(dòng)作放在終結(jié)器不就行了么。為什么還要我實(shí)現(xiàn)IDisposable
          接口,然后讓使用者手動(dòng)釋放呢?

          C++ 版本的析構(gòu)函數(shù):
          class Line { public: Line(); ~Line(); private: double length; };
          C# 版本的終結(jié)器:
          public class Line { private double _length; public Line() { } ~Line() { } }
          二、原因

          說起這個(gè)原因,首先得從 C# 終結(jié)器的 調(diào)用時(shí)機(jī) 說起。終結(jié)器的調(diào)用是 CLR 在進(jìn)行 GC
          時(shí),如果某個(gè)對(duì)象寫有終結(jié)器,即便它應(yīng)該被釋放,也不會(huì)馬上回收該對(duì)象。而 C++ 的析構(gòu)函數(shù)是確定性析構(gòu),取決于你調(diào)用 delete 的時(shí)機(jī)。

          GC 會(huì)將其添加到一個(gè)隊(duì)列當(dāng)中,單獨(dú)使用了一個(gè) 高優(yōu)先級(jí) 線程去調(diào)用對(duì)象的終結(jié)器。因?yàn)橐WC線程能夠訪問到終結(jié)器對(duì)象,所以本該釋放的對(duì)象,以及對(duì)象相關(guān)的資源就
          會(huì)被提升 1 代 ,會(huì) 增加內(nèi)存占用。

          一旦終結(jié)器方法帶有死循環(huán),那么 GC 將永遠(yuǎn)無法釋放該資源,造成 內(nèi)存泄漏。

          除開內(nèi)存占用增大的原因,如果你在終結(jié)器方法內(nèi)部引用了其他帶終結(jié)器對(duì)象,GC 無法保證終結(jié)器調(diào)用順序,所以你可能訪問到的對(duì)象是已經(jīng)終結(jié)了的。

          還有一種情況會(huì)導(dǎo)致尷尬的內(nèi)存泄漏,本來對(duì)象 A 應(yīng)該被釋放了,結(jié)果你在終結(jié)器內(nèi)部又讓其他的根保持對(duì)象的引用,又會(huì)讓這個(gè)對(duì)象復(fù)活。因?yàn)?GC
          只會(huì)執(zhí)行一次帶終結(jié)器對(duì)象的終結(jié)器。執(zhí)行一次過后,就再也不會(huì)執(zhí)行對(duì)象的終結(jié)器了。
          public class BadClass { private static readonly List<BadClass> _list = new
          List<BadClass>(); private string _msg; public BadClass(string msg) { _msg =
          (string)msg.Clone(); } ~BadClass() { // 造成 _msg 的內(nèi)存不會(huì)被釋放。 _list.Add(this); } }
          三、最佳實(shí)踐

          針對(duì) Effective C# 所提出的最佳實(shí)踐,你應(yīng)該為對(duì)象實(shí)現(xiàn) IDisposable
          接口,以釋放托管資源。如果你對(duì)象確實(shí)使用了非托管資源,那么你也應(yīng)該為其編寫終結(jié)器。因?yàn)榉峭泄苜Y源的,你不能保證調(diào)用者能夠顯示調(diào)用Dispose()
          方法,所以你得通過終結(jié)器來處理。

          一個(gè)典型的 Dispose() 方法應(yīng)該將托管資源、非托管資源全部進(jìn)行釋放,設(shè)置對(duì)應(yīng)的標(biāo)識(shí)表明對(duì)象已經(jīng)被釋放了,阻止垃圾回收器重復(fù)清理該對(duì)象、保證方法的
          冪等性。
          public class FatherClass : IDisposable { private bool isDisposed = false;
          public void Dispose() { Dispose(true); // 通知 GC,這個(gè)對(duì)象已經(jīng)完全被清理。
          GC.SuppressFinalize(this); } ~FatherClass() { Dispose(false); } protected
          virtual Dispose(bool isDisposing) { if(isDisposed) return; if(isDisposing) { //
          釋放托管資源。 } // 釋放非托管資源。 isDisposed = true; } public void TestMethod() {
          if(isDisposed) { throw new ObjectDisposedException("對(duì)象已經(jīng)被釋放。"); } } } public
          class ChildClass : FatherClass { private bool isDisposed = false; protected
          override void Dispose(bool isDisposing) { if(isDisposed) return;
          if(isDisposing) { // 釋放托管資源。 } base.Dispose(isDisposing); isDisposed = true; } }
          在上面的實(shí)踐中,我們提煉出了一個(gè) void Dispose(bool)
          方法,并將其設(shè)置為虛函數(shù)。這樣做的好處有兩點(diǎn),第一點(diǎn)是方便子類重寫釋放邏輯,第二點(diǎn)是可以將終結(jié)器和Dispose() 方法內(nèi)部重復(fù)的代碼提煉出來。

          友情鏈接
          ioDraw流程圖
          API參考文檔
          OK工具箱
          云服務(wù)器優(yōu)惠
          阿里云優(yōu)惠券
          騰訊云優(yōu)惠券
          京東云優(yōu)惠券
          站點(diǎn)信息
          問題反饋
          郵箱:[email protected]
          QQ群:637538335
          關(guān)注微信

                亚洲午夜无码久久 | 中国操屄视频 | 青青草福利视频 | 国产视频一区不卡 | 亚洲欧美乱伦 |