本文轉自:
當我們使用非託管資源(unmanaged resources)類型時,應當使用IDisposable介面的Dispose()方法來釋放資源。在.Net環境中,對非託管資源的回收不是系統的責任,我們必須自己調用Dispose()方法來釋放資源。確保非託管資源會釋放的最好方法是使用using或者try/finally。
所有的非託管資源類型都實現了IDisposable介面。另外當我們沒有明確的釋放資源,比如我們忘記了,C#還會防護性的通過建立終結器(finalizer)來釋放資源。如果我們希望應用程式啟動並執行更快時,就應當儘快釋放一些不必要的資源。幸運的是在C#中有新的關鍵字來完成這項任務。我們先考慮下面的代碼:
儲存
public void ExcuteCommand(string connectString, string commandString){SqlConnection myConnection = new SqlConnection(connectString);SqlCommand myCommand = new SqlCommand(commandString, myConnection);myConnection.Open();myCommand.ExecuteNonQuery();}
有兩個對象沒有被釋放掉:SqlConnection和SqlCommand。它們都會儲存在記憶體中直到終結器被調用為止。
通過下面的修改,我們可以釋放它們:
儲存
public void ExcuteCommand(string connectString, string commandString){SqlConnection myConnection = new SqlConnection(connectString);SqlCommand myCommand = new SqlCommand(commandString, myConnection);myConnection.Open();myCommand.ExecuteNonQuery();myCommand.Dispose();myConnection.Dispose();}
這樣做是正確的,但前提是SqlCommand沒有拋出異常。一旦出現異常,我們的Dispose()方法就不會運行了。using關鍵字可以協助我們確保Dispose()會被運行。當我們使用using的時候,C#的編譯器會將它轉換成為類似與try/finally的形式:
儲存
public void ExcuteCommand(string connectString, string commandString){using (SqlConnection myConnection = new SqlConnection(connectString)){using (SqlCommand myCommand = new SqlCommand(commandString, myConnection)){myConnection.Open();myCommand.ExecuteNonQuery();}}}
下例中的兩段代碼會產生非常相似的IL
儲存
using (SqlConnection myConnection = new SqlConnection(connectString)){myConnection.Open();}try{SqlConnection myConnection = new SqlConnection(connectString);myConnection.Open();}finally{myConnection.Dispose();}
當我們使用非託管資源時,使用using是確保資源合理釋放的簡單途徑。如果我們對不支援IDisposable介面的類型使用using關鍵字,編譯器會報錯:
儲存
//錯誤using(string msg = "this is a message"){Console.WriteLine(msg);}
另外using只檢驗編譯時間類型是否支援IDisposable介面,它不能識別運行時的對象。下例中即便Factory.CreateResource()返回的類型支援IDisposable介面也是不能通過編譯的:
儲存
//錯誤using(object obj = Factory.CreateResource){}
對於可能支援可能不支援IDisposable介面的對象,我們可以這樣來處理:
儲存
object obj = Factory.CreateResource();using(obj as IDisposable){}
如果對象實現了IDisposable介面,就可以產生釋放資源的代碼。如果不支援,則產生using(null),雖然不做任何工作,但也是安全的。如果我們拿不準是否應該將對象放在using中,那麼比較穩妥的做法是將它放進去。
當我們在程式中使用了非託管資源類型時,我們應當將其放入using的括弧中。當有多個需要釋放的資源,例如前面的例子中的connection和command,我們應當建立多個using,每一個包含一個對應的對象。這些using會被轉化為不同的try/finally塊,在效果上看就好像是下面這段代碼:
儲存
public void ExcuteCommand(string connectString, string commandString){SqlConnection myConnection = null;SqlCommand myCommand = null;try{myConnection = new SqlConnection(connectString);try{myCommand = new SqlCommand(commandString, myConnection);myConnection.Open();myCommand.ExecuteNonQuery();}finally{if (myCommand != null){myCommand.Dispose();}}}finally{if (myConnection != null){myConnection.Dispose();}}}
每個using聲明都建立了一個try/finally程式塊。我們自己也可以通過這樣寫來取消多層嵌套:
儲存
public void ExcuteCommand(string connectString, string commandString){SqlConnection myConnection = null;SqlCommand myCommand = null;try{myConnection = new SqlConnection(connectString);myCommand = new SqlCommand(commandString, myConnection);myConnection.Open();myCommand.ExecuteNonQuery();}finally{if (myCommand != null){myCommand.Dispose();}if (myConnection != null){myConnection.Dispose();}}}
雖然看起來很簡潔,但是我們不要這樣使用using聲明:
儲存
public void ExcuteCommand(string connectString, string commandString){SqlConnection myConnection = new SqlConnection(connectString);SqlCommand myCommand = new SqlCommand(commandString, myConnection);using (myConnection as IDisposable){using (myCommand as IDisposable){myConnection.Open();myCommand.ExecuteNonQuery();}}}
這樣做是有潛在bug的。一旦SqlCommand的建構函式拋出異常,SqlConnection就無法被釋放了。我們必須保證每個非託管資來源物件都可以被順利的釋放,否則可能會造成記憶體資源的浪費。對於單個的非託管資來源物件,使用using關鍵字是最好的方法。對於多個對象,我們可以使用嵌套using或者自己寫try/finally的方法來釋放資源。
在釋放資源上還有一個細節,有些類型不僅有Dispose()方法,還有Close()方法,例如SqlConnection。我們這樣可以關閉SqlConnection的串連:
儲存
myConnection.Close();
這樣做的確能夠釋放串連,但是並不是和Dispose()方法一樣。Dispose()方法除了釋放資源之外,還有其他的工作:它會通知垃圾收集器(Garbage Collector)這個對象的資源已經被釋放了,而不必在終結器中進行重複的操作。Dispose()調用了GC.SuppressFinalize()方法,這個方法請求系統不要調用指定對象的終結器。而Close()不是這樣的。使用Close()釋放的對象雖然已經不必調用終結器,但是它還是存在於終結器的釋放資源隊列當中。Dispose()比Close()的工作做的更加徹底。
Dispose()並沒有將對象移出記憶體。它為對象添加了一個釋放資源的鉤子(hook)。這就意味著我們可以對正在使用的對象使用Dispose(),我們應當小心這一點。
在C#中大部分的類型都不支援Dispose()。在超過1500種類型中只有100來種實現了IDispose介面。當我們使用實現了這個介面的對象時,我們應當在適當的時候使用using或try/finally塊的方法來釋放資源。
譯自 Effective C#:50 Specific Ways to Improve Your C# Bill Wagner著
回到目錄
P.S. 以下為個人想法:
Dispose()和Close()都是可以顯示調用來釋放資源。而Finalize()是受保護的,解構函式更是不知道什麼時候會被執行。被Close()掉的對象是有可能再次複活的,比如SqlConnection,還可以通過Open()繼續使用,好像是休眠狀態。而被Dispose()掉的對象從理論上來說是不應當再複活了,因為我們在Dispose的同時應當告訴GC這個對象已經over了,以避免重複的對象清除工作。當然我們可以通過釋放資源時獲得其引用的詭異方法來繼續訪問對象,但是由於Dispose()調用GC.SuppressFinalize()方法免除終結,因此這些Dispose()掉又複活的對象的資源就再也不會自動被GC釋放了。
儲存
public class DisposeClass : IDisposable{private string insideResources;public void UseInsideResources(){insideResources = "use resouces";Console.WriteLine(insideResources);}public DisposeClass(){Console.WriteLine("create object");}~DisposeClass(){Console.WriteLine("destroy~ object");}#region IDisposable 成員public void Dispose(){// TODO: 添加 DisposeClass.Dispose 實現 Console.WriteLine("dispose object");GC.SuppressFinalize(this);}#endregion}
上例中的類在使用Dispose()釋放資源後是不會調用解構函式的,沒有調用Dispose()時才會在需要終結時調用解構函式。但是如果我們這樣使用它:
儲存
static void Main(string[] args){ArrayList myList = new ArrayList();using (DisposeClass a = new DisposeClass()){myList.Add(a);}((DisposeClass) (myList[0])).UseInsideResources();Console.ReadLine();}
已經被Dispose掉的對象又複活了。而且解構函式不會再被調用。我們同樣可以在解構函式中做這種事,只是我們不知道它什麼時候會發生而已。
Finalize()是一個神奇的函數,在沒有解構函式時它可以像諸如abc()一樣做為普通的成員函數,但是一旦解構函式存在,就會報出“已經存在名為Finalize()成員”的錯誤。更匪夷所思的是當我們將其聲明為保護成員函數(開始時我將其聲明為public的,不會出現特殊的結果,後來才改成protected)時,它就會被其衍生類別的解構函式調用。在C#中,衍生類別的解構函式會自動調用基類的解構函式,因此在這裡衍生類別看來已經把它當作是基類的解構函式了。在編譯器中應該將它同解構函式划了等號了吧,可是object.Finalize()明明是不能重寫的。對於protected void Finalize()來說,它到底算是個什麼東西呢?這應該是在編譯階段就可以發現的錯誤吧?代碼如下:
儲存
static void Main(string[] args){ExtendDisposeClass a = new ExtendDisposeClass();Console.ReadLine();}public class DisposeClass : IDisposable{public DisposeClass(){Console.WriteLine("create object");}// ~DisposeClass() // { // Console.WriteLine("destroy~ object"); // }protected void Finalize(){Console.WriteLine("finalize object");}#region IDisposable 成員public void Dispose(){// TODO: 添加 DisposeClass.Dispose 實現 Console.WriteLine("dispose object");}#endregion}public class ExtendDisposeClass : DisposeClass{~ExtendDisposeClass(){Console.WriteLine("destroyExtendDisposeClass object");}}
釋放個資源可真是不簡單啊...