BOWL

關於C# 的using 陳述式在實務應用上相關觀念

為何需要用到using或者在別人的代碼中看到using,你會不會也有這個疑問呢?

在使用using時需要注意的是,using只能用於實現IDisposable介面的類.

Common language runtime 的垃圾收集行程會回收 managed 物件所使用的記憶體,但使用非受控資源的類型 IDisposable 會執行介面,以允許回收這些非受控資源所需的資源。 實作 IDisposable 的物件使用完畢時,您應呼叫物件的 IDisposable.Dispose 實作。 您可以使用下列其中一種作法:

  • 使用 c # using 語句 (Using Visual Basic) 。
  • 藉由執行 try/finally 區塊,並 IDisposable.Dispose 在中呼叫 finally 。

提供方便的語法,以確保正確使用 IDisposable 物件。 從 c # 8.0 開始, using 語句可確保正確使用 IAsyncDisposable 物件。

IDisposable⇒提供用於釋放 Unmanaged 資源的機制。

IAsyncDisposable ⇒提供用於非同步釋放非受控資源的機制。

範例(資料庫連接與釋放)

錯誤作法:

//連接資料庫
SqlConnection conn = new SqlConnection();

//在這邊做輸入或者是查詢動作
//如果在之前有任何出錯的情況下,並不會即時關閉資料庫連接
conn.Dispose();

正確作法:

//連接資料庫
SqlConnection conn = new SqlConnection();
try
{
    //在這邊做輸入或者是查詢動作
}
finally
{
    conn.Dispose();
}

優化作法:

//連接資料庫
using(SqlConnection conn = new SqlConnection())
{
      //在這邊做輸入或者是查詢動作
}
public class DisposeExampleTest
{
    public class MyResource: IDisposable
    {
        // 屬於非託管資源
        private IntPtr handle;
        // 屬於其他託管資源
        private Component component = new Component();
        // 透過旗標方式來了解資源是否釋放
        private bool disposed = false;

        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // 使用IDisposable.
        public void Dispose()
        {
            Dispose(true);
	// 此物件將被釋放透過此Dispose方法
	// 因此,您應該調用GC.SupressFinalize將該對象移出終結隊列,
	//並防止該對象的終結代碼再次執行。
            GC.SuppressFinalize(this);
        }

	 // Dispose(bool dispose)在兩個不同的場景中執行。
         //如果dispose等於true,則直接調用該方法
         //或通過用戶代碼間接獲得。可以處置託管和非託管資源。
         //如果處置等於false,則方法已由在終結器內部運行時,您不應引用
         //其他對象。只能處置非託管資源。
        protected virtual void Dispose(bool disposing)
        {
            //檢查是否已調用Dispose。
            if(!this.disposed)
            {
                //如果處置等於true,則處置所有託管和非託管資源。
                if(disposing)
                {
                    //處置託管資源.
                    component.Dispose();
                }
//調用適當的方法進行清理這裡的非託管資源。如果處理方式為假,僅執行以下代碼。
                CloseHandle(handle);
                handle = IntPtr.Zero;
                //注意處理已完成。
                disposed = true;
            }
        }

	//使用互操作來調用必要的方法
        //清理非託管資源。
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

	//對最終代碼使用C#析構函數語法。
        //此析構函數僅在Dispose方法下運行不會被調用。
        //它為您的基類提供了完成的機會。不要提供從此類派生的類型的析構函數。
        ~MyResource()
        {
            //不要在此處重新創建清理代碼。
            //就以下方面而言,調用Dispose(false)是最佳的可讀性和可維護性。
            Dispose(false);
        }
    }
    public static void Main()
    {
        //在此處插入代碼以創建
        //並使用MyResource對象。
    }
}

使用實作 IDisposable 的物件

類別會執行 StreamReader IDisposable 介面,這表示它會使用非受控資源。

C# using 陳述式還可讓您以單一陳述式取得多項資源,其內部相當於巢狀的 using 陳述式。

你一定想說SteamReader也有Dispose的方法嗎? 答案是有的,詳細請參考連結

using System.IO;

class Example
{
    static void Main()
    {
        char[] buffer1 = new char[50];
        char[] buffer2 = new char[50];

        using StreamReader version1 = new StreamReader("fileA.txt"),
                           version2 = new StreamReader("fileB.txt");

        int charsRead1, charsRead2 = 0;
        while (version1.Peek() != -1 && version2.Peek() != -1)
        {
            charsRead1 = version1.Read(buffer1, 0, buffer1.Length);
            charsRead2 = version2.Read(buffer2, 0, buffer2.Length);
            //
            // Process characters read.
            //
        }
    }
}

Try/finally 區塊

如果您的程式語言不支援 using 陳述式,但是允許直接呼叫 Dispose 方法,而使得您選擇實作或必須實作 try/finally 區塊,則可以遵循下面這個模式。

using System;
using System.Globalization;
using System.IO;

class Example
{
    static void Main()
    {
        StreamReader? streamReader = null;
        try
        {
            streamReader = new StreamReader("Test1.txt");
            string contents = streamReader.ReadToEnd();
            var info = new StringInfo(contents);
            Console.WriteLine($"The file has {info.LengthInTextElements} text elements.");
        }
        catch (FileNotFoundException)
        {
            Console.WriteLine("The file cannot be found.");
        }
        catch (IOException)
        {
            Console.WriteLine("An I/O error has occurred.");
        }
        catch (OutOfMemoryException)
        {
            Console.WriteLine("There is insufficient memory to read the file.");
        }
        finally
        {
            streamReader?.Dispose();
        }
    }
}

結論

使用using的類別需要實作IDisposable介面跟實作void Dispose()方法。另外using 還有一個好處,可以確保物件不會在 Dispose 之後被使用。

Posted inC#