1. 程式人生 > >碼農深耕 - 說說IDisposable

碼農深耕 - 說說IDisposable

概要

C#提供了方便的垃圾回收機制,使我們幾乎不再需要為資源管理費心。可事實上,能被垃圾回收釋放掉的只是託管資源,非託管資源還是需要我們手動釋放。而為了實現這一目的,C#提供了 IDisposable 介面,這篇文章就談一談 IDisposable 介面在使用中需要注意的地方。

實現

首先,IDisposable 介面非常簡單,只包含一個方法 Dispose。在 IDisposable 介面的定義中可以看到明確的描述,它是用於釋放非託管資源的 [1]。但是,想寫出一個健壯的 IDisposable 實現卻不是那麼容易,好在微軟為我們提供了一份詳細的指南 [2][3],參照這份指南中的示例程式碼,我們就可以輕而易舉的寫出一份優秀的 IDisposable 介面實現程式碼了,這裡不再進行詳細說明。

那麼那些資源是常見的非託管資源呢?根據我的經驗,列舉如下:

  • 檔案流
  • 窗體控制代碼
  • 圖片
  • 網路連線
  • 資料庫連線

在使用到上述資源時,不要忘記務必在使用之後呼叫它們的 Dispose 方法。為了保證資源釋放,一般我們會利用 try / finally 塊,在 finally 塊中呼叫 Dispose 方法。針對這種需求,C# 為我們提供了 using 語法糖 [4],對於實現了 IDisposable 介面的物件,利用 using 語句,可以簡單的完成資源釋放。

需要注意的細節

注意事件退訂

當我們呼叫了一個物件的 Dispose 方法之後,它的非託管資源就被釋放掉了,但是這個物件仍然可以被訪問。因此,如果在這個物件內訂閱了其他物件的事件,務必在 Dispose 方法中將事件退訂 [5]。否則,事件釋出者再次觸發事件時,這個已經釋放掉資源的物件還是會響應該事件,如果在事件響應方法中嘗試訪問已經釋放的資源,則會發生意料外的錯誤。

WinForm 控制元件

從父容器中移除控制元件

當我們從一個容器中將某個控制元件 Remove 掉,這個控制元件的控制代碼並不會被釋放,如果我們忘記顯示地呼叫該控制元件的 Dispose 方法,又頻繁地建立、移除控制元件,很快就會因為控制代碼過多而發生異常,面對這種情況,往往一頭霧水,很難找到發生異常的根本原因。在以往的工作中,我一般會選擇將一個控制元件從容器中 Remove 之後,再呼叫該控制元件的 Dispose 方法。後來無意間發現直接呼叫控制元件的 Dispose 方法,它會自動將自己從父容器中移除,通過閱讀原始碼 [6] 證實了這個特性,真的挺方便。

移除自己的子控制元件

上面提到呼叫一個控制元件的 Dispose 方法,會自動將自己從父容器中移除。那麼 Dispose 方法會對自己的子控制元件產生什麼影響呢?是否需要在呼叫 Dispose 之前,先遍歷並釋放所有子控制元件呢?答案是不用,控制元件會自動呼叫所有子控制元件的 Dispose 方法,通過原始碼 [7] 可以證實這一點。可見,控制元件的 Dispose 方法是沒有副作用的,一旦呼叫,就可以帶著自己的資源,消失在我們的系統中,這種實現的思路,值得我們借鑑學習。

結語

IDisposable 為我們提供了便利,彌補了自動垃圾回收的不足,掌握好這個介面,不僅可以使我們的開發水平更進一步,也可以讓我們的產品穩定性更上層樓。

參考文獻:

[1] IDisposable 介面 (https://docs.microsoft.com/en-us/dotnet/api/system.idisposable?view=netframework-4.7.2#definition)
[2] 清理非託管資源 (https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/unmanaged?view=netframework-4.7.2)
[3] Dispose 模式 (https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/dispose-pattern)
[4] using 語法糖 (https://docs.microsoft.com/en-us/dotnet/api/system.idisposable?view=netframework-4.7.2#the-c-and-visual-basic-using-statement)
[5] 取消訂閱 (https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/events/how-to-subscribe-to-and-unsubscribe-from-events#unsubscribing)
[6] 從父容器中移除 (https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,6013)
[7] 自動釋放子控制元件的資源 (https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,6017)