程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> .NET實例教程 >> 正確實現 IDisposable 接口

正確實現 IDisposable 接口

編輯:.NET實例教程

     正確實現 IDisposable
  
  .Net中用於釋放對象資源的接口是IDisposable,但是這個接口的實現還是比較有講究的,此外還有Finalize和Close兩個函數。
  
  MSDN建議按照下面的模式實現IDisposable接口:
  
   1 public class Foo: IDisposable
   2 {
   3 public void Dispose()
   4 {
   5 Dispose(true);
   6 GC.SuppressFinalize(this);
   7 }
   8
   9 protected virtual void Dispose(bool disposing)
  10 {
  11 if (!m_disposed)
  12 {
  13 if (disposing)
  14 {
  15 // Release managed resources
  16 }
  17
  18 // Release unmanaged resources
  19
  20 m_disposed = true;
  21 }
  22 }
  23
  24 ~Foo()
  25 {
  26 Dispose(false);
  27 }
  28
  29 private bool m_disposed;
  30 }
  31
  32
  
  
  在.Net的對象中實際上有兩個用於釋放資源的函數:Dispose和Finalize。Finalize的目的是用於釋放非托管的資源,而Dispose是用於釋放所有資源,包括托管的和非托管的。
  
  
  
  在這個模式中,void Dispose(bool disposing)函數通過一個disposing參數來區別當前是否是被Dispose()調用。如果是被Dispose()調用,那麼需要同時釋放托管和非托管的資源。如果是被~Foo()(也就是C#的Finalize())調用了,那麼只需要釋放非托管的資源即可。
  
  
  
  這是因為,Dispose()函數是被其它代碼顯式調用並要求釋放資源的,而Finalize是被GC調用的。在GC調用的時候Foo所引用的其它托管對象可能還不需要被銷毀,並且即使要銷毀,也會由GC來調用。因此在Finalize中只需要釋放非托管資源即可。另外一方面,由於在Dispose()中已經釋放了托管和非托管的資源,因此在對象被GC回收時再次調用Finalize是沒有必要的,所以在Dispose()中調用GC.SuppressFinalize(this)避免重復調用Finalize。
  
  
  
  然而,即使重復調用Finalize和Dispose也是不存在問題的,因為有變量m_disposed的存在,資源只會被釋放一次,多余的調用會被忽略過去。
  
  
  
  因此,上面的模式保證了:
  
  
  
  1、 Finalize只釋放非托管資源;
  
  2、 Dispose釋放托管和非托管資源;
  
  3、 重復調用Finalize和Dispose是沒有問題的;
  
  4、 Finalize和Dispose共享相同的資源釋放策略,因此他們之間也是沒有沖突的。
  
  
  
  在C#中,這個模式需要顯式地實現,其中C#的~Foo()函數代表了Finalize()。而在C++/CLI中,這個模式是自動實現的,C++的類析構函數則是不一樣的。
  
  
  
  按照C++語義,析構函數在超出作用域,或者delete的時候被調用。在Managed C++(即.NET 1.1中的托管C++)中,析構函數相當於CLR中的Finalize()方法,在垃圾收集的時候由GC調用,因此,調用的時機是不明確的。在.Net 2.0的C++/CLI中,析構函數的語義被修改為等價與Dispose()方法,這就隱含了兩件事情:
  
  
  
  1、 所有的C++/CLI中的CLR類都實現了接口IDisposable,因此在C#中可以用using關鍵字來訪問這個類的實例。
  
  2、 析構函數不再等價於Finalize()了。
  
  
  
  對於第一點,這是一件好事,我認為在語義上Dispose()更加接近於C++析構函數。對於第二點,Microsoft進行了一次擴展,做法是引入了“!”函數,如下所示:
  
  1 public ref class Foo
  2 {
  3 public:
  4 Foo();
  5 ~Foo(); // destructor
  6 !Foo(); // finalizer
  7 };
  8
  
  
  “!”函數(我實在不知道應該怎麼稱呼它)取代原來Managed C++中的Finalize()被GC調用。MSDN建議,為了減少代碼的重復,可以寫這樣的代碼:
  
   1 ~Foo()
   2 {
   3 //釋放托管的資源
   4 this->!Foo();
   5 }
   6
   7 !Foo()
   8 {
   9 //釋放非托管的資源
  10 }
  11
  
  
  對於上面這個類,實際上C++/CLI生成對應的C#代碼是這樣的:
  
  
  
  
   1 public class Foo
   2 {
   3 private void !Foo()
   4 {
   5 // 釋放非托管的資源
   6 }
   7
   8 private void ~Foo()
   9 {
  10 // 釋放托管的資源
  11 !Foo();
  12 }
  13
  14 public Foo()
  15 {
  16 }
  17
  18 public void Dispose()
  19 {
  20 Dispose(true);
  21 GC.SuppressFinalize(this);
  22 }
  23
  24 protected virtual void Dispose(bool disposing)
  25 {
  26 if (disposing)
  27 {
  28 ~Foo();
  29 }
  30 else
  31 {
  32 try
  33 {
  34 !Foo();
  35 }
  36 finally
  37 {
  38 base.Finalize();
  39 }
  40 }
  41 }
  42
  43 protected void Finalize()
  44 {
  45 Dispose(false);
  46 }
  47 }
  48
  
  
  由於~Foo()和!Foo()不會被重復調用(至少MS這樣認為),因此在這段代碼中沒有和前面m_disposed相同的變量,但是基本的結構是一樣的。
  
  
  
  並且,可以看到實際上並不是~Foo()和!Foo()就是Dispose和Finalize,而是C++/CLI編譯器生成了兩個Dispose和Finalize函數,並在合適的時候調用它們。C++/CLI其實已經做了很多工作,但是唯一的一個問題就是依賴於用戶在~Foo()中調用!Foo()。
  
  
  
  關於資源釋放,最後一點需要提的是Close函數。在語義上它和Dispose很類似,按照MSDN的說法,提供這個函數是為了讓用戶感覺舒服一點,因為對於某些對象,例如文件,用戶更加習慣調用Close()。
  
  
  
  然而,畢竟這兩個函數做的是同一件事情,因此MSDN建議的代碼就是:
  
  
  
  1 public void Close()
  2 {
  3 Dispose(();
  4 }
  5
  6
  這裡直接調用不帶參數的Dispose函數以獲得和Dispose相同的語義。這樣似乎就圓滿了,但是從另外一方面說,如果同時提供了Dispose和Close,會給用戶帶來一些困惑。沒有看到代碼細節的前提下,很難知道這兩個函數到底有什麼區別。因此在.Net的代碼設計規范中說,這兩個函數實際上只能讓用戶用一個。因此建議的模式是:
  
   1 public class Foo: IDisposable
   2 {
   3 public void Close()
   4 {
   5 Dispose();
   6 }
   7
   8 void IDisposable.Dispose()
   9 {
  10 Dispose(true);
  11 GC.SuppressFinalize(this);
  12 }
  13
  14 protected virtual void Dispose(bool disposing)
  15 {
  16 // 同前
  17 }
  18 }
  19
  
  
  這裡使用了一個所謂的接口顯式實現:void IDisposable.Dispose()。這個顯式實現只能通過接口來訪問,但是不能通過實現類來訪問。因此:
  
  
  1 Foo foo = new Foo();
  2
  3 foo.Dispose(); // 錯誤
  4 (foo as IDisposable).Dispose(); // 正確
  5
  
  
  這樣做到了兼顧兩者。對於喜歡使用Close的人,可以直接用 foo.Close(),並且他看不到 Dispose()。對於喜歡Dispose的,他可以把類型轉換為 IDisposable 來調用,或者使用using語句。兩者皆大歡喜!
  http://www.cnblogs.com/xlshcn/archive/2007/01/16/idisposable.Html
  

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved