《Effective C#》讀書筆記——條目15:使用using和try/finally清理資源<.NET資源管理>


  在.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             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和下面的示例是一樣的:

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                 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語句包裹起來。無論使用哪種方式,都要保證對象在任何時候、任何情況下都被正確的銷毀。

 

參考資源&進一步閱讀

終結器

using 語句

實現 Finalize 和 Dispose 以清理非托管資源


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM