在.NET環境中,非託管系統資源由開發人員來負責釋放,且非託管系統資源必須顯式的使用IDisposable介面的Dispose()來釋放(詳見:瞭解.NET記憶體管理機制)。所有封裝或使用了非託管資源的類型都實現了IDisposable介面。同時這些類也在終結器中調用Dispose(),保證了開發人員在忘記資源釋放的時候仍然能夠正常的釋放掉資源(對象資源會一直停留在記憶體中,直到終結器被調用),這會導致資源在記憶體中停留的時間更長,導致應用程式會佔用更多的系統資源。
閱讀目錄:
1.使用using關鍵字
1.1 安全銷毀對象
2.同時銷毀多個可銷毀對象
3.釋放可銷毀對象的方式
小節
參考&進一步閱讀
1.使用 using 關鍵字
在C#中為我們添加了一個現實釋放非託管資源的關鍵字:using。using語句其實是一個C#語言的文法糖,當我們在using語句中分配可釋放對象時,C#編譯器將會自動在每個對象外產生一個try/finally塊來包裹住分配的對象,保證資源的及時釋放,即使拋出了異常也一樣。如果要使用一個可銷毀的對象,使用using語句能夠以最簡單的方式保證你的對象可以正常銷毀。看下面使用了using語句的代碼:
1 SqlConnection myConnection = null;2 3 using (myConnection = new SqlConnection(connectionString))4 {5 myConnection.Open();6 }
這段代碼和下面的代碼產生的IL代碼完全一致:
1 try2 {3 myConnection = new SqlConnection(connectionString);4 myConnection.Open();5 }6 finally7 {8 myConnection.Dispose();9 }
使用using語句的注意事項:
如果using語句中分配的變數的類型沒有實現IDisposable介面,編譯器將會拋出異常。
1.1 安全銷毀對象
對於一些可能實現或未實現IDisposable介面的對象,或者無法確定是否應該用using語句包裹某個對象時,由於其不確定性我們可以使用as操作符進行安全的銷毀,如下:
1 object obj = Factory.CreateResource();2 //如果不確定obj是否實現了IDisposable介面,下面是安全的銷毀方式3 using (obj as IDisposable)4 {5 Console.WriteLine(obj.ToString());6 }
如果obj實現了IDisposable介面,那麼using語句將產生清理代碼;反之,using語句將變成using(null) ,這並不會拋出異常,也不會有任何其他意料之外的操作。
2.同時銷毀多個可銷毀對象
通過前面我們知道使用using語句時編譯器會將其轉換為try/finally語句,這能夠保證我們可以正確的將非託管對象銷毀。但是當我們想要同時銷毀多個對象時情況會有一點細微的變化,看下面的代碼:
1 public void ExecuteCommand(string connString, string commandString) 2 { 3 using (SqlConnection myConnection = new SqlConnection(connString)) 4 { 5 using (SqlCommand mySqlCommand = new SqlCommand(commandString, myConnection)) 6 { 7 myConnection.Open(); 8 mySqlCommand.ExecuteNonQuery(); 9 }10 }11 }
我們看到方法內部有兩個非託管對象需要被釋放,上面的代碼啟動並執行效果和我們的預期並沒有差別,我們通過查看IL代碼發現,這個樣本產生的IL和下面的樣本是一樣的:
View Code
1 public void ExecuteCommand(string connString, string commandString) 2 { 3 SqlConnection myConnection = null; 4 SqlCommand mySqlCommand = null; 5 try 6 { 7 myConnection = new SqlConnection(connString); 8 try 9 {10 mySqlCommand = new SqlCommand(commandString, myConnection);11 12 myConnection.Open();13 mySqlCommand.ExecuteNonQuery();14 }15 finally16 {17 if (mySqlCommand != null)18 mySqlCommand.Dispose();19 }20 }21 finally22 {23 if (myConnection != null)24 myConnection.Dispose();25 }26 }
上面的代碼並沒有上面問題,運行效果可以達到了我們的預期,不過我們可以做得好點,直接使用try/finally語句,避免在這樣情況中使用using語句而產生了多餘的try/finally語句,這樣寫會更好:
1 public void ExecuteCommand(string connString, string commandString) 2 { 3 SqlConnection myConnection = null; 4 SqlCommand mySqlCommand = null; 5 try 6 { 7 myConnection = new SqlConnection(connString); 8 mySqlCommand = new SqlCommand(commandString, myConnection); 9 10 myConnection.Open();11 mySqlCommand.ExecuteNonQuery();12 }13 finally14 {15 if (mySqlCommand != null)16 mySqlCommand.Dispose();17 if (myConnection != null)18 myConnection.Dispose();19 }
注意:
必須保證每個實現了IDisposable介面的對象都放在了using或try/finally中,否則就可能會發生資源流失。
3.釋放可銷毀對象的方式
我們發現在我們釋放可銷毀對象時。有的類型不但提供Dispose()方法還提供了一個Close方法,比如前面樣本中SqlConnection類的myConnection對象,我們可以知道調用myConnection對象的Close方法關閉資料庫連接,但是這和調用它的Dispose()有一些差別:Dispose()方法將調用GC.SuppressFinalize()方法,而Close()方法一般則不會,因此,即使已經不需要終結,但對象仍舊在終結隊列中。如果兩種方式你可以選擇,應該優先使用Dispose方法。
同時我們需要知道:Dispose()並不是將對象從記憶體中移除,而只是讓對象釋放掉其中的非託管資源。
小節
.Net Framework 中只有不到100個類實現了IDisposable介面,當我們使用實現了IDisposable介面的類時,我們要保證能夠正確的進行清理工作,可以將這些對使用using語句或者是try/finally語句包裹起來。無論使用哪種方式,都要保證對象在任何時候、任何情況下都被正確的銷毀。
參考資源&進一步閱讀
終結器
using 語句
實現 Finalize 和 Dispose 以清理非託管資源