在.NET環境中,非托管系統資源由開發人員來負責釋放,且非托管系統資源必須顯式的使用IDisposable接口的Dispose()來釋放(詳見:了解.NET內存管理機制)。所有封裝或使用了非托管資源的類型都實現了IDisposable接口。同時這些類也在終結器中調用Dispose(),保證了開發人員在忘記資源釋放的時候仍然能夠正常的釋放掉資源(對象資源會一直停留在內存中,直到終結器被調用),這會導致資源在內存中停留的時間更長,導致應用程序會占用更多的系統資源。
閱讀目錄:
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 try 2 { 3 myConnection = new SqlConnection(connectionString); 4 myConnection.Open(); 5 } 6 finally 7 { 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和下面的示例是一樣的:

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 finally 16 { 17 if (mySqlCommand != null) 18 mySqlCommand.Dispose(); 19 } 20 } 21 finally 22 { 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 finally 14 { 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語句包裹起來。無論使用哪種方式,都要保證對象在任何時候、任何情況下都被正確的銷毀。