程式設計小知識之 Dispose 模式

2021-09-09 05:37:25 字數 3757 閱讀 9958

本文簡述了 c# 中 dispose 模式的一些知識

之前對 c# 中的 dispose 模式只有些模糊印象,近來又了解了一些相關知識,在此簡單做些記錄~

c# 程式中每種型別都可以看做是一種資源,這些資源可以分成兩類:

託管資源 : 受 clr 管理(分配和釋放)的資源,譬如 new 出的某個型別物件

非託管資源 : 不受 clr 管理(分配和釋放)的資源,譬如某個 native 的檔案控制代碼

對於託管資源,由於受 clr 的管理,大部分情況下我們都不用操心資源的釋放問題,但是對於非託管資源,由於不受 clr 的管理,釋放的問題便必須我們自己來做了.

那麼我們通過什麼方法來釋放這些非託管資源呢, c# 提供了乙個標準介面idisposable:

public inte***ce idisposable

如果你程式中的某個型別需要釋放非託管資源,就讓他實現idisposable介面,也就是通過void dispose()方法來實現非託管資源的釋放, 示例**如下:

// dispose pattern v1

public class disposepattern : idisposable

public void dispose()

} // close handle method

[system.runtime.interopservices.dllimport("kernel32")]

extern static bool closehandle(intptr handle);

}

上面的示例**有個很大的問題,如果外部**沒有呼叫 disposepattern 的 dispose 方法,那麼 disposepattern 持有的非託管資源(m_handle)便洩露了.

就程式設計規範來講,其實是應該規避外部**不呼叫 dispose 方法的行為,如果這可以做到,那麼示例**中的 dispose 實現便已經足夠了,但是這在實際中往往難以保證(或者說做到保證的成本太高),另外從實現的角度來看, disposepattern 如果能在外部**不呼叫 dispose 方法的前提下仍然保證非託管資源不洩露,那麼程式也會更加健壯.

如何實現呢?我們需要借助 c# 中的析構函式(或者叫終結器)

這裡我們暫時不去關注 c# 中析構函式的各個細節,只要知道析構函式可以在型別被**之前執行就行了,新的示例**如下:

// dispose pattern v2

public class disposepattern : idisposable

// destructor

~disposepattern() }

public void dispose()

// get rid of ~disposepattern() call

gc.suppressfinalize(this); }

// close handle method

[system.runtime.interopservices.dllimport("kernel32")]

extern static bool closehandle(intptr handle);

}

可以看到我們額外定義了~disposepattern(),並在其中實現了非託管資源的釋放,這就保證了即使外部**不呼叫 dispose 方法,非託管資源也能正確釋放(在 disposepattern **之前),相對的,如果外部**呼叫了 dispose 方法,我們便不需要再呼叫 ~disposepattern() 了(當然,這裡只是說不需要,不是說不可以,這裡在 dispose 之後繼續呼叫 ~disposepattern() 也是可以的,這也是出於健壯性的考慮), dispose() 方法中的gc.suppressfinalize(this);便是用來"遮蔽"析構函式的執行的(定義了析構函式的型別可以通過呼叫 gc.suppressfinalize 來抑制析構函式的執行).

實際的**中,乙個型別除了持有非託管資源,自然也會持有託管資源,如果這些託管資源(型別)也實現了idisposable介面(或者更廣義的來說,實現了 dispose 之類的釋放資源方法.這裡我們將問題標準化(簡化),規定實現釋放資源方法就需要實現idisposable介面)

最終的實現**如下:

// dispose pattern v3

public class disposepattern : idisposable

// release external unmanaged resource

if (m_handle != intptr.zero)

m_disposed = true;

} }public disposepattern(intptr handle)

// destructor

~disposepattern()

public void dispose()

// close handle method

[system.runtime.interopservices.dllimport("kernel32")]

extern static bool closehandle(intptr handle);

}

上面示例中的 disposepattern 實現便是所謂的dispose 模式,**中的幾個要點還需要細細說明一下:

我們抽象了乙個內部的void dispose(bool disposing)方法來輔助我們的 dispose 模式實現,這主要是出於**可讀性和可維護性的考慮.

新增加的bool m_disposed成員主要是為了解決外部**重複呼叫(之前說明的是不呼叫和僅呼叫一次) dispose() 方法的問題(之前其實也存在重複呼叫的問題,只是我們通過if (m_handle != intptr.zero)這種編碼方式規避了)

void dispose(bool disposing) 方法的引數bool disposing的意思,是用來區分 dispose 呼叫路徑的(是外部**呼叫還是析構函式呼叫),如果是外部**呼叫,我們一併釋放託管資源和非託管資源,如果是析構函式呼叫,我們僅釋放非託管資源(託管資源在他們各自的析構函式中進行 dispose),至於為何需要做這種區分,可以簡單理解為這是 dispose 模式的實現規範(想繼續了解的同學可以進一步看看後面的說明).

參考資料

改善c#程式的建議4:c#中標準dispose模式的實現

idisposable inte***ce

why using finalizers is a bad idea

更多說明

之前我們提到, dispose 模式中區分了 dispose 的呼叫路徑(如果是外部**呼叫,我們一併釋放託管資源和非託管資源,如果是析構函式呼叫,我們僅釋放非託管資源),這裡可以引出幾個問題:

如果是外部**呼叫,我們可以不釋放託管資源嗎(標準實現是一併釋放託管資源和非託管資源)?

如果是析構函式呼叫,我們可以釋放託管資源嗎(標準實現是僅釋放非託管資源)?

程式設計小知識之 Dithering

本文簡單介紹了 dithering 抖動 的一些知識 圖形後處理有一種操作稱為 dithering 抖動 所謂 dithering,就是一種能夠在較小色彩空間上 模擬出 較大色彩空間的影象處理方法,說的有些抽象,我們來舉個例子 假設我們需要在顯示器上顯示以下 來自這裡 的畫素格式為 rgb 24rg...

網路程式設計小知識

pdu 協議資料單元,有隱藏size上限,如果應用程式的包超過指定上限會被劃分為多個pdu傳送 tcp不提供記錄結束標記,需要應用程式自己提供,比如http的 r n 編寫tcp協議需要注意ipv4和ipv6的相容性,可以在應用程式中實現協議無關性。getaddrinfo 多執行緒的socket程式...

程式設計小知識之 Lua 長度運算子

本文講解了 lua 中長度運算子 的一些知識 注 以下討論基於 lua 5.3.5 版本 基礎 lua 中的長度運算子 可以用於獲取 table 的 長度 舉個簡單的例子 local t print t 3但其實對於 table 而言,長度運算子並不等同於獲取 table 的 長度 更準確一些的說法...