C#:using與.net對象銷燬

一 、程序員

1.using 語句獲取一個或多個資源,執行一個語句,而後處置該資源。     
2.using 語句: 
using ( 資源獲取 ) 嵌入語句 
3.資源獲取: 
局部變量聲明 
表達式 

資源是實現 System.IDisposable 的類或結構,它包含名爲 Dispose 的單個無參數方法。(如:圖2)正在使用資源的代碼能夠調用 Dispose 以指示再也不須要該資源。若是不調用 Dispose,則最終將由於垃圾回收而發生自動處置。

若是資源獲取的形式是局部變量聲明,那麼此局部變量聲明的類型必須爲System.IDisposable 或是能夠隱式轉換爲 System.IDisposable 的類型。若是資源獲取的形式是表達式,那麼此表達式必須是 System.IDisposable 或是能夠隱式轉換爲 System.IDisposable 的類型。 

在資源獲取中聲明的局部變量必須是隻讀的,且必須包含一個初始值設定項。 

using 語句被翻譯成三個部分:獲取、使用和處置。資源的使用隱式封閉在包含一個 finally 子句的 try語句中。此 finally 子句處置資源。若是獲取了 null 資源,則不進行對 Dispose 的調用,也不引起任何異常。 

例如,下列形式的 using 語句 

using (R r1 = new R()) { 
r1.F(); 

徹底等效於 

R r1 = new R(); 
try { 
r1.F(); 

finally { 
if (r1 != null) ((IDisposable)r1).Dispose(); 



圖1:數據庫

    using確保執行IDisposable接口的對象在退出塊時當即釋放,主要是爲了防止忘記關閉數據庫鏈接可能致使的.net可執行程序的各類問題。 
框架

圖2:ide

 

2、函數

CLR 有一個垃圾收集GC機制,能夠管理內存分配和回收等工做,在絕大多數狀況下,程序員只須要new 一個對象,而將銷燬這一對象的工做徹底交給CLR代勞。性能

可是,咱們所編寫的類中使用了非託管的資源,好比文件句柄,用於線程同步的Mutex對象,或者是數據庫鏈接,這些資源應該遵循「即需即建即銷燬」的原則,這就是說:須要的時候才建立這些對象,用完以後就立刻銷燬。this

析構函數(destructor) 與構造函數相反,當對象脫離其做用域時(例如對象所在的函數已調用完畢),系統自動執行析構函數。析構函數每每用來作「清理善後」 的工做(例如在創建對象時用new開闢了一片內存空間,應在退出前在析構函數中用delete釋放)。spa


以C++語言爲例,析構函數名也應與類名相同,只是在函數名前面加一個波浪符~,例如~stud( ),以區別於構造函數。它不能帶任何參數,也沒有返回值(包括void類型)。只能有一個析構函數,不能重載。若是用戶沒有編寫析構函數,編譯系統會自動生成一個缺省的析構函數,它也不進行任何操做。因此許多簡單的類中沒有用顯式的析構函數。

解構器.net

     咱們知道,‘解構器’被用來清除類的事例。當咱們在C#中使用解構器是,咱們必須記住如下幾點:線程

       一個類只能有一個解構器。 
       解構器不能被繼承或重載。 
       解構器不能被調用。他們是自動被(編譯器)調用的。 
       解構器不能帶修飾或參數。 
   下面是類MyClass解構器的一個聲明:

 

~ Class()   
{  
    // Cleaning up code goes here  
}

 

      程序員不能控制解構器什麼時候將被執行由於這是由垃圾收集器決定的。垃圾收集器檢查不在被應用程序使用的對象。它認爲這些條件是符合清楚的而且收回它們的內存。解構器也在程序退出時被調用。當解構器執行時其背後所發生的那一幕是解構器隱式調用對象基類的Object.Finalize方法。所以上述解構器代碼被隱含轉化成:

 

protected override void Finalize()  
{  
    try  
    {  
       // Cleaning up .  
    }  
    finally  
    {  
       base.Finalize();  
    }
}

     如今,讓咱們看一個解構器怎樣被調用的例子。咱們有三個類A,B和C 。B派生自A,C派生自B。每一個類有它們本身的構造器和解構。在類App的main函數中,咱們建立C的對象。

 

using System;  
class A  
{  
public A()  
{  
   Console.WriteLine("Creating A");  
}  
~A()  
{  
   Console.WriteLine("Destroying A");  
}  
}  
   
class B:A  
{  
public B()  
{  
   Console.WriteLine("Creating B");  
}  
~B()  
{  
   Console.WriteLine("Destroying B");  
}  
   
}  
class C:B  
{  
public C()  
{  
   Console.WriteLine("Creating C");  
}  
   
~C()  
{  
   Console.WriteLine("Destroying C");  
}  
}  
class App  
{  
public static void Main()  
{  
   C c=new C();  
   Console.WriteLine("Object Created ");  
   Console.WriteLine("Press enter to Destroy it");  
   Console.ReadLine();  
   c=null;  
   //GC.Collect();  
   Console.Read();  
}  
}
  
     正如咱們預料的,基類的構造器將會被執行而且程序會等待用戶按‘enter’。當這個發生,咱們把類C的對象置爲null.但解構器沒有被執行..!!??正像咱們所說的,程序員沒法控制解構器什麼時候被執行由於這是由垃圾蒐集器決定的。但程序退出時解構器被調用了。你能經過重定向程序的o/p到文本文件來檢查這個。我將它輸出在這裏。注意到基類的解構器被調用了,由於在背後base.Finalize()被調用了。

 

Creating A
Creating B
Creating C
Object Created 
Press enter to Destroy it
Destroying C
Destroying B
Destroying A
 
     因此,若是一旦你使用完對象你就想調用解構器,你該怎麼作?有兩個方法:

調用垃圾蒐集器來清理。

實現IDisposable的Dispose方法。

 

調用垃圾蒐集器

 

     你能經過調用GC.Collect方法強制垃圾蒐集器來清理內存,但在大多數狀況下,這應該避免由於它會致使性能問題。在上面的程序中,在GC.Collect()處移除註釋。編譯並運行它。如今,你能看到解構器在控制檯中被執行了。

 

 

以過上面的分析,咱們要明確,當CLR的垃圾收集線程要回收一個定義了析構函數的對象時,它會自動調用其Finalize方法。

析構函數存在的主要目的是釋放非託管的資源。可是,對象的析構函數(即Finalize方法)被調用的時機是不可控的,由於它的調用由CLR的垃圾收集機制

負責,出於性能考慮,CLR的垃圾收集線程只是在「它認爲是合適的」時機才運行,這樣一來就有可能出現對象所佔有的非託管資源遲遲不能獲得釋放的狀況。

 

最好有一種方法可以讓程序員主動地以徹底可控的方式去釋放這些非託管的資源,爲此.NET提供了一個IDispose接口。

 

實現IDisposable接口

 

     IDisposable 接口包括僅有的一個公共方法,其聲明爲void Dispose()。咱們能實現這個方法來關閉或釋放非託管資源如實現了這個接口的類事例所控制的文件,流,和句柄等。這個方法被用作全部任務聯合對象的資源釋放。當實現了這個方法,對象必須尋求確保全部擁有的資源被繼承結構中關聯的資源也釋放(不能把握,翻不出來)。

 

class MyClass:IDisposable  
{  
     public void Dispose()  
{  
   //implementation  
}  
}

 

     當咱們實現了IDisposable接口時,咱們須要規則來確保Dispose被適當地調用。

  這裏有一個問題,CLR不認爲IDisposable接口與其餘接口有什麼不一樣,因CLR的垃圾收集線程不會主動地詢問對象是否實現了IDisposable接口

並自動調用它的Dispose方法。另外,不少可能程序員在開發時會忘記調用對象的Dispose方法,爲了不資源泄露,咱們讓對象的析構函數也調用Dispose方法,

 這就雙保險了。如如下代碼所示:

 

聯合使用解構器和IDisposable接口

 

Public class MyClass:IDisposable  
{  
private bool IsDisposed=false;  
public void Dispose()  
{  
   Dispose(true);  
   GC.SupressFinalize(this);  
}  
protected void Dispose(bool Diposing)  
{  
   if(!IsDisposed)  
   {  
   if(Disposing)  
   {  
    //Clean Up managed resources  
   }  
   //Clean up unmanaged resources  
}  
IsDisposed=true;  
}  
~MyClass()  
{  
   Dispose(false);  
}  
}

 

這個代碼框架看上去不太好理解,首先,給類添加一個Dispose(bool),此方法接收一個bool類型的參數,當參數值爲true 時,能夠編寫一些必要的代碼來清理託管的資源,好比

調用本對象所引用的其餘託管對象的Dispose方法。無論參數值如何,此Dispose(bool)方法都必須完成清理非託管資源的任務。

當用戶在應用 程序中顯式調用IDisposable接口的Dispose方法時,此方法以true做爲實參調用上面定義的虛方法Dispose(bool),並通知CLR的垃圾收集線程,不要再調用此對象的Finalize方法。

若是用戶沒有顯式調用IDisposable接口的Dispose方法,則由CLR的垃圾收集線程負責調用對象的Finalize方法完成資源清理工做,注意這時傳入的參數是false,國爲finalize方法只負責釋放非託管的資源。

~MyClass()

{

    Dispose(false);

}

     在這裏重載了Dispose(bool)來作清理工做,而且全部的清理代碼都僅寫在這個方法中。這個方法被解構器和IDisposable.Dispose()兩着調用。咱們應該注意Dispose(bool)沒有在任何地方被調用除了在IDisposable.Dispose()和解構器中。

     當一個客戶調用IDisposable.Dispose()時,客戶特地地想要清理託管的和非託管資源,而且所以完成清理工做。有一件你必須注意的事情是咱們在清理資源以後當即調用了GC.SupressFinalize(this)。這個方法通知垃圾蒐集器不須要調用解構器,由於咱們已經作了清理。

     注意上面的例子,解構器使用參數false調用Dispose。這裏,咱們確信垃圾蒐集器蒐集了託管資源。咱們僅僅作非託管資源的清理。

 

結論

 

     儘管如此咱們花費一些時間實現IDisposable接口,若是客戶不能合適地調用它們會怎樣?爲此C#有一個酷的解決方案。‘using’代碼塊。它看起來像這樣:

using (MyClass objCls =new MyClass())  
{  
   
}
 
     當控制從using塊經過成功運行到結束或者拋出異常退出時,MyClass的IDispose.Dispose()將會被執行。記住你例示的對象必須實現System.IDisposable接口。using語句定義了哪一個對象將被清除的一個範圍。

注:
   構造函數與析構函數的區別:
      構造函數和析構函數是在類體中說明的兩種特殊的成員函數。
      構造函數的功能是在建立對象時,使用給定的值來將對象初始化。
      析構函數的功能是用來釋放一個對象的。在對象刪除前,用它來作一些清理工做,它與構造函數的功能正好相反。

相關文章
相關標籤/搜索