程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> Effective C#原則15:使用using和try/finally來做資源清理

Effective C#原則15:使用using和try/finally來做資源清理

編輯:關於C#

使用非托管資源的類型必須實現IDisposable接口的Dispose()方法來精確的 釋放系統資源。.Net環境的這一規則使得釋放資源代碼的職責是類型的使用者, 而不是類型或系統。因此,任何時候你在使用一個有Dispose()方法的類型時, 你就有責任來調用Dispose()方法來釋放資源。最好的方法來保證Dispose()被調 用的結構是使用using語句或者try/finally塊。

所有包含非托管資源的 類型應該實現IDisposable接口,另外,當你忘記恰當的處理這些類型時,它們 會被動的創建析構函數。如果你忘記處理這些對象,那些非內存資源會在晚些時 候,析構函數被確切調用時得到釋放。這就使得這些對象在內存時待的時間更長 ,從而會使你的應用程序會因系統資源占用太多而速度下降。

幸運的是 ,C#語言的設計者精確的釋放資源是一個常見的任務。他們添加了一個關鍵字 來使這變得簡單了。

假設你寫了下面的代碼:

public void ExecuteCommand( string connString,
 string commandString )
{
 SqlConnection myConnection = new SqlConnection( connString );
 SqlCommand mySqlCommand = new SqlCommand( commandString,
  myConnection );
 myConnection.Open();
 mySqlCommand.ExecuteNonQuery();
}

這個例子中的兩 個可處理對象沒有被恰當的釋放:SqlConnection和SqlCommand。兩個對象同時 保存在內存裡直到析構函數被調用。(這兩個類都是從 System.ComponentModel.Component繼承來的。)

解決這個問題的方法就 是在使用完命令和鏈接後就調用它們的Dispose:

public void ExecuteCommand( string connString,
 string commandString )
{
 SqlConnection myConnection = new SqlConnection( connString );
 SqlCommand mySqlCommand = new SqlCommand( commandString,
  myConnection );
 myConnection.Open();
  mySqlCommand.ExecuteNonQuery();
 mySqlCommand.Dispose( );
 myConnection.Dispose( );
}

這很好,除非SQL命令在執 行時拋出異常,這時你的Dispose()調用就永遠不會成功。using語句可以確保 Dispose()方法被調用。當你把對象分配到using語句內時,C#的編譯器就把這 些對象放到一個try/finally塊內:

public void ExecuteCommand( string connString,
 string commandString )
{
 using ( SqlConnection myConnection = new
   SqlConnection( connString ))
 {
  using ( SqlCommand mySqlCommand = new
   SqlCommand( commandString,
    myConnection ))
  {
   myConnection.Open();
    mySqlCommand.ExecuteNonQuery();
  }
 }
}

當你在一個函數內使用一個可處理對象時,using語句是最簡單 的方法來保證這個對象被恰當的處理掉。當這些對象被分配時,會被編譯器放到 一個try/finally塊中。下面的兩段代碼編譯成的IL是一樣的:

SqlConnection myConnection = null;
// Example Using clause:
using ( myConnection = new SqlConnection( connString ))
{
 myConnection.Open();
}
// example Try / Catch block:
try {
 myConnection = new SqlConnection( connString );
 myConnection.Open();
}
finally {
  myConnection.Dispose( );
}
(譯注:就我個人對 try/catch/finally塊的使用經驗而言,我覺得上面這樣的做法非常不方便。可 以保證資源得到釋放,卻無法發現錯誤。關於如何同時拋出異常又釋放資源的方 法可以參考一下其它相關資源,如Jeffrey的.Net框架程序設計,修訂版)

如果你把一個不能處理類型的變量放置在using語句內,C#編譯器給出一個錯誤 ,例如:

// Does not compile:
// String is sealed, and does not support IDisposable.
using( string msg = "This is a message" )
 Console.WriteLine( msg );

using只能在編譯時,那些支持IDispose接口的類型可以使用 ,並不是任意的對象:

// Does not compile.
// Object does not support IDisposable.
using ( object obj = Factory.CreateResource( ))
 Console.WriteLine( obj.ToString( ));

如果obj實現了IDispose接口,那麼using語句就會生成資源 清理代碼,如果不是,using就退化成使用using(null),這是安全的,但沒有任 何作用。如果你對一個對象是否應該放在using語句中不是很確定,寧可為了更 安全:假設要這樣做,而且按前面的方法把它放到using語句中。

這裡講 了一個簡單的情況:無論何時,當你在某個方法內使用一個可處理對象時,把這 個對象放在using語句內。現在你學習一些更復雜的應用。還是前面那個例子裡 須要釋放的兩個對象:鏈接和命令。前面的例子告訴你創建了兩個不同的using 語句,一個包含一個可處理對象。每個using語句就生成了一個不同的 try/finally塊。等效的你寫了這樣的代碼:

public void ExecuteCommand( string connString,
 string commandString )
{
 SqlConnection myConnection = null;
 SqlCommand mySqlCommand = null;
 try
 {
  myConnection = new SqlConnection( connString );
  try
  {
    mySqlCommand = new SqlCommand( commandString,
   myConnection );
   myConnection.Open();
    mySqlCommand.ExecuteNonQuery();
  }
  finally
   {
   if ( mySqlCommand != null )
     mySqlCommand.Dispose( );
  }
 }
 finally
  {
  if ( myConnection != null )
    myConnection.Dispose( );
 }
}

每一個using語句 生成了一個新的嵌套的try/finally塊。我發現這是很糟糕的結構,所以,如果 是遇到多個實現了IDisposable接口的對象時,我更願意寫自己的try/finally塊 :

public void ExecuteCommand( string connString,
  string commandString )
{
 SqlConnection myConnection = null;
 SqlCommand mySqlCommand = null;
 try {
   myConnection = new SqlConnection( connString );
  mySqlCommand = new SqlCommand( commandString,
   myConnection );
   myConnection.Open();
  mySqlCommand.ExecuteNonQuery();
 }
 finally
 {
  if ( mySqlCommand != null )
   mySqlCommand.Dispose();
  if ( myConnection != null )
   myConnection.Dispose();
 }
}

(譯注:作 者裡的判斷對象是否為null是很重要的,特別是一些封裝了COM的對象,有些時 候的釋放是隱式的,當你再釋放一些空對象時會出現異常。例如:同一個COM被 兩個不同接口的變量引用時,在其中一個上調用了Dispose後,另一個的調用就 會失敗。在.Net裡也要注意這樣的問題,所以要判斷對象是否為null)

然 而,請不要自作聰明試圖用as來寫這樣的using語句:

public void ExecuteCommand( string connString,
 string commandString )
{
 // Bad idea. Potential resource leak lurks!
  SqlConnection myConnection =
  new SqlConnection( connString );
 SqlCommand mySqlCommand = new SqlCommand( commandString,
   myConnection );
   using ( myConnection as IDisposable )
   using (mySqlCommand as IDisposable )
   {
     myConnection.Open();
    mySqlCommand.ExecuteNonQuery ();
   }
}

這看上去很清爽,但有一個狡猾的 (subtle )的bug。 如果SqlCommand()的構造函數拋出異常,那麼SqlConnection 對象就不可能被處理了。你必須確保每一個實現了IDispose接口的對象分配在在 using范圍內,或者在try/finally塊內。否則會出現資源洩漏。

目前為 止,你已經學會了兩種最常見的情況。無論何時在一個方法內處理一個對象時, 使用using語句是最好的方法來確保申請的資源在各種情況下都得到釋放。當你 在一個方法裡分配了多個(實現了IDisposable接口的)對象時,創建多個using塊 或者使用你自己的try/finally塊。

對可處理對象的理解有一點點細微的 區別。有一些對象同時支持Disponse和Close兩個方法來釋放資源。 SqlConnection就是其中之一,你可以像這樣關閉SqlConnection:

public void ExecuteCommand( string connString,
  string commandString )
{
 SqlConnection myConnection = null;
 try {
  myConnection = new SqlConnection( connString );
  SqlCommand mySqlCommand = new SqlCommand( commandString,
   myConnection );
  myConnection.Open ();
  mySqlCommand.ExecuteNonQuery();
 }
  finally
 {
  if ( myConnection != null )
    myConnection.Close();
 }
}

這個版本關閉了鏈接 ,但它確實與處理對象是不一樣的。Dispose方法會釋放更多的資源,它還會告 訴GC,這個對象已經不再須要析構了(譯注:關於C#裡的析構,可以參考其它方 面的書籍)。Dispose會調用GC.SuppressFinalize(),但Close()一般不會。結果 就是,對象會到析構隊列中排隊,即使析構並不是須要的。當你有選擇時, Dispose()比Colse()要好。你會在原則18裡學習更更精彩的內容。

Dispose()並不會從內存裡把對象移走,對於讓對象釋放非托管資源來說 是一個hook。這就是說你可能遇到這樣的難題,就是釋放一個還在使用的對象。 不要釋放一個在程序其它地方還在引用的對象。

在某些情況下,C#裡的 資源管理比C++還要困難。你不能指望確定的析構函數來清理你所使用的所有資 源。但垃圾回收器卻讓你更輕松,你的大從數類型不必實現IDisposable接口。 在.Net框架裡的1500多個類中,只有不到100個類實現了IDisposable接口。當你 使用一個實現了IDisposeable接口的對象時,記得在所有的類裡都要處理它們。 你應該把它們包含在using語句中,或者try/finally塊中。不管用哪一種,請確 保每時每刻對象都得到了正確的釋放。

返回教程目錄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved