1. 程式人生 > >可以使用 using 或者 try-catch-finally 呼叫 Dispose() 來釋放非託管資源

可以使用 using 或者 try-catch-finally 呼叫 Dispose() 來釋放非託管資源

1.我們常使用IDisposable介面的Dispose()方法來精確的釋放非託管系統資源。在日常程式碼中我們確保呼叫Dispose()的方法是用using和try/catch/finally語句。

2.所有包含非託管資源的型別都會實現IDisposable介面,他們還會建立終結器,以防止我們忘記呼叫Dispose()。如果你忘記呼叫Dispose(),那些非記憶體資源會在晚些時候,終結器會呼叫時會釋放這些資源。這就使得這些物件在記憶體時待的時間更長,從而會使你的應用程式會因系統資源佔用太多而速度下降。

3.程式碼展示:

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( );
}

4.我們不能對一個沒有實現IDispose介面的物件使用using方式,編譯器會報錯。

5.每一個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();
        }
}

6.有一些物件同時支援Disponse和Close兩個方法來釋放資源。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,這個物件已經不再須要析構了(注:這個物件本身並沒有刪除出記憶體)。它是通過呼叫GC.SuppressFinalize()來實現這一點的。但Close()一般不會,因此呼叫了close()的物件仍然留在終結列表中,雖然這個時候不需要終結器的操作了。當你有選擇時,Dispose()比Colse()要好。

7.我們應該對經常出現的區域性變數提升為類的成員變數(當在一個方法中經常呼叫一個變數時候,方法呼叫結束後,垃圾收集器就會去處理這個變數,每呼叫一次垃圾收集器就要增加額外的開銷),對那些是非託管資源的變數升級為成員變數我們還應該把類設計成繼承IDisposable介面,在介面方法中釋放非託管資源。下面舉例說明:

protected override void OnPaint( PaintEventArgs e )
{
using ( Font MyFont = new Font( "Arial", 10.0f ))
{
    e.Graphics.DrawString( DateTime.Now.ToString(),
      MyFont, Brushes.Black, new PointF( 0,0 ));
}
base.OnPaint( e );
}

OnPaint()函式的呼叫很頻繁的,每次呼叫它的時候,都會生成另一個Font物件,而實際上它是完全一樣的內容。垃圾回收器每次都須要清理這些物件。這將是難以置信的低效。 取而代之的是,把Font物件從區域性變數提供為物件成員,在每次繪製視窗時重用同樣的物件:

private readonly Font _myFont =new Font( "Arial", 10.0f );

protected override void OnPaint( PaintEventArgs e )
{
      e.Graphics.DrawString( DateTime.Now.ToString( ), _myFont, Brushes.Black, new PointF( 0,0 ));
      base.OnPaint( e );
}

8.所有的引用型別(值型別沒關係),即使是區域性變數都分配在堆上。當垃圾回收器回收堆上的物件時候會花費而外的時間,隨著函式的退出,引用型別的區域性變數就會變成垃圾。這裡強調的是在頻繁的呼叫的時候,如果呼叫的很少我們沒有必要把區域性變數升級為成員變數。

9.前面例子中使用的靜態屬性Brushes.Black,演示了另一個避免重複建立相似物件的技術。使用靜態成員變數來建立一些常用的引用型別的例項。考慮前面那個例子裡使用的黑色畫刷,每次當你要用黑色畫刷來畫一些東西時,你要在程式中建立和釋放大量的黑色畫刷。前面的一個解決方案就是在每個期望黑色畫刷的類中新增一個畫刷成員,但這還不夠。程式可能會建立大量的視窗和控制元件,這同樣會建立大量的黑色畫刷。.Net框架的設計者預知了這個問題,他們為你建立一個簡單的黑色畫刷以便你在任何地方都可以重複使用。(也就是設計模式中的單例模式)

private static Brush _blackBrush;//類的靜態成員
public static Brush Black//靜態屬性
{
    get
     {
           if ( _blackBrush == null )//這裡也可以用於多執行緒的處理
           _blackBrush = new SolidBrush( Color.Black );
          return _blackBrush;
      }
}

10.System.String類就是一個恆定型別,在你建立一個字串後,它的內容就不能更改了。當你編寫程式碼來修改這些串的內容時,你實際上是建立了新的物件,並且讓舊的串成為了垃圾。

string msg = "Hello, ";
msg += thisUser.Name;
msg += ". Today is ";
msg += System.DateTime.Now.ToString();

字串tmp1,tmp2,tmp3以及最原始的msg構造的(“Hello”),都成了垃圾。+=方法在字串類上會生成一個新的物件並返回它。對於更多的複雜的字串操作,你應該使用StringBuilter類。通過StringBuilter學到了一種思想:一些要經過多次構造後才能最終得到的物件,可以考慮使用一些物件生成器來簡化物件的建立。它提供了一個方法讓你的使用者來逐步的建立(你設計的)恆定型別,也用於維護這個型別。

11.初始化常量型別通常有三種策略,選擇哪一種策略依賴於一個型別的複雜度。定義一組合適的構造器通常是最簡單的策略。也可以建立一個工廠方法(factory method)來進行初始化工作。這種方式對於建立一些常用的值比較方便。.NET框架中的Color型別就採用了這種策略來初始化系統顏色。例如,靜態方法Color.FromKnownColor()和Color.FromName()可以根據一個指定的系統顏色名,來返回一個對應的顏色值。最後,對於需要多個步驟操作才能完整構造出一個常量型別的情況,我們可以通過建立一個可變的輔助類來解決。.NET中的String類就採用了這種策略,其輔助類為System.Text.StringBuilder。我們可以使用StringBuilder類通過多步操作來建立一個String物件。