CSharp的介面顯式實現和IDisposable介面與using關鍵字的關係
近日,有同事寫的http下載模組出現了一些問題,在Review程式碼的過程中發現一個奇怪的地方:
針對從WebResponse中取出來的Stream,在用完以後,對於Stream手動依次呼叫了Close、Dispose。
if (reader != null)
{
reader.Close();
reader.Dispose();
}
而緊接著,對於WebResponse的操作卻僅僅呼叫了Close。
if (response != null)
{
response.Close();
}
而實際上,針對WebResponse的使用範例,無一例外都是使用了using關鍵字來處理Dispose的。
using (WebResponse response = request.GetResponse())
{
...
response.Close();
}
這表明,WebResponse在使用完後,需要進行Dispose,於是我嘗試給他加上Dispose呼叫,卻發現通過response的引用,無法點出Dispose來,詢問原作者後,也表示因為點不出來,所以認為沒這個介面,就沒有呼叫了。但是,檢視WebResponse的定義,發現它是有繼承IDisposable的。
namespace System.Net { public abstract class WebResponse : MarshalByRefObject, IDisposable, ISerializable { ... } }
那麼,為什麼會點不出Dispose方法來呢?
原來,WebResponse對IDisposable介面的實現採用了顯式實現。
void IDisposable.Dispose()
{
...
}
對於顯式實現的介面,無法在子類的引用中呼叫該實現,而必須使用基類的引用。
if (response != null)
{
response.Close();
(response as IDisposable).Dispose();
}
這樣,可以成功呼叫Dispose,對其進行釋放。
那麼,為什麼使用using關鍵字時可以成功釋放呢?那是因為using在CSharp中的潛規則是,將賦予它的引用,強轉為IDisposable,然後呼叫它的Dispose介面。
using (object)
{
...
}
上述程式碼相當於
...
(object as IDisposable).Dispose();
特別的,如果傳入的object並沒有繼承自IDisposable,編譯時會直接報錯。
對於踩了這樣一個坑,說明這位同事的基礎知識掌握還不夠,並且在技術選型時有些盲目自信,在沒有完全掌握基礎細節的情況下,擅自改變了範例程式碼的常見用法,導致了這個結果。
在此,呼籲各位基礎還不紮實的同學,在學習範例程式碼的時候要麼徹底搞明白這麼寫的原因,要麼就嚴格模仿,不要擅自去修改,以免採坑,好自為之。