2017年9月14日 星期四

C# : 處置模式 Dispose 設計模式

在這份筆記中,我們來進行 Dispose 設計模式的設計與測試。

了解更多關於 [實作 Dispose 方法
了解更多關於 [C# 程式設計手冊 

在這裡,我們宣告一個類別,並且需要使用 IDisposable 這個介面,所以,我們對於這個類別的宣告為 public class MyClass : IDisposable
接著,將游標移動到 IDisposable 上,接著滑動到電燈泡上,從彈出選單中,選擇 使用 Dispose 模式實作介面,此時,Visual Studio 會幫您自動時做出類似下列類別宣告程式碼。我們僅需要在 TODO 標示處,根據您的需要,將相關的程式碼實作上去,這樣,這個類別就具有了 處置模式 的能力了。
在這裡,我們分別在建構函式與解構函式內,輸出訊息,說明這個物件現在要被回收還是要被建立,不過,在這裡,我們還將當時受管理執行緒的 ID 也顯示出來;透過這些受管理的執行緒 ID,我們可以得知,當時的解構式是在哪個執行緒上運行的。
若解構函式是由Finalize來執行,此時,將會是在一個特殊的執行緒上執行,而不是在主執行緒上執行,因此,對於要在另外一個執行緒上執行並且修改共享的資料的時候,請務必要遵循跨執行緒的程式設計規範,否則,這個程式會有問題。
    public class MyClass : IDisposable
    {
        public string MyProperty { get; set; } = "這是屬性";
        public MyClass()
        {
            Console.WriteLine($"MyClass 類別的物件已經被建立了,執行緒為 {Thread.CurrentThread.ManagedThreadId}");
        }

        #region IDisposable Support
        private bool disposedValue = false; // 偵測多餘的呼叫

        protected virtual void Dispose(bool disposing)
        {
            Console.WriteLine($"protected virtual void Dispose Ticks={DateTime.UtcNow.Ticks}");
            Console.WriteLine($"Dispose 方法被執行了,執行緒為 {Thread.CurrentThread.ManagedThreadId}");
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: 處置 Managed 狀態 (Managed 物件)。
                    // 例如,可以將綁定的事件解除
                }

                // TODO: 釋放 Unmanaged 資源 (Unmanaged 物件) 並覆寫下方的完成項。
                // TODO: 將大型欄位設為 null。

                disposedValue = true;
            }
        }

        // TODO: 僅當上方的 Dispose(bool disposing) 具有會釋放 Unmanaged 資源的程式碼時,才覆寫完成項。
        ~MyClass()
        {
            // 請勿變更這個程式碼。請將清除程式碼放入上方的 Dispose(bool disposing) 中。
            Dispose(false);
            Console.WriteLine($"Destructor Ticks={DateTime.UtcNow.Ticks}");
            Console.WriteLine($"MyClass 類別的物件已經被記憶體回收程序釋放了,執行緒為 {Thread.CurrentThread.ManagedThreadId}");
        }

        // 加入這個程式碼的目的在正確實作可處置的模式。
        public void Dispose()
        {
            Console.WriteLine($"public void Dispose Ticks={DateTime.UtcNow.Ticks}");
            // 請勿變更這個程式碼。請將清除程式碼放入上方的 Dispose(bool disposing) 中。
            Dispose(true);
            // TODO: 如果上方的完成項已被覆寫,即取消下行的註解狀態。
            //GC.SuppressFinalize(this);
        }
        #endregion

    }

進行測試

首先,我們先來測試 TestMethod1 方法的程式碼。
在這裡,我們建立一個物件,接著,將持有這個物件的物件變數設定成為空值,我們就緊接著進行強制的資源回收程序。
private static void TestMethod1()
{
    Console.WriteLine($"建立 myClassObject物件");
    MyClass myClassObject = new MyClass();
    Console.WriteLine("Press any key for continuing...");
    Console.ReadKey();

    Console.WriteLine($"準備強制進行記憶體回收程序");
    Console.WriteLine($"Ticks={DateTime.UtcNow.Ticks}");
    myClassObject = null;
    GC.Collect();
    Console.WriteLine("Press any key for continuing...");
    Console.ReadKey();
}
從底下的輸出結果,我們看到了,建立物件的執行緒為 1,這表示主執行緒為 ID = 1,可是,我們看到了,執行解構函式的執行緒 ID = 2。
而且 Dispose 方法被執行了,執行緒為 2 Dispose 的方法,也不是在主執行緒上運行。
執行的輸出結果
建立 myClassObject物件
MyClass 類別的物件已經被建立了,執行緒為 1
Press any key for continuing...
準備強制進行記憶體回收程序
Ticks=636409963642699536
Press any key for continuing...
protected virtual void Dispose Ticks=636409963642729535
Dispose 方法被執行了,執行緒為 2
Destructor Ticks=636409963642729535
MyClass 類別的物件已經被記憶體回收程序釋放了,執行緒為 2
-------------------------------
Press any key for continuing...
接下來,我們來看看第二個測試方法
因為這個類別有實作 IDisposable 這個介面,因此,我們在使用這個物件的時候,使用 using 將其包起來,因此,當結束 using 區段之後,就會自動呼叫這個類別 Dispose 方法,而且,呼叫這個方法所使用到的執行緒,與建立這個物件所用到的執行緒,皆為相同,都是主執行緒。
private static void TestMethod2()
{
    MyClass myClassObject;
    Console.WriteLine($"建立 myClassObject物件");
    using (myClassObject = new MyClass())
    {
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }

    Console.WriteLine($"準備強制進行記憶體回收程序");
    Console.WriteLine($"Ticks={DateTime.UtcNow.Ticks}");
    myClassObject = null;
    GC.Collect();
    Thread.Sleep(3000);
    Console.WriteLine("Press any key for continuing...");
    Console.ReadKey();
}
不過,當要測試這個方法的時候,在 Debug 與 Release 建置模式下,所跑出來的結果輸出文字會有些不同,不過,這是因為在 Release 模式下,編譯器會進行不同程度的優化處理,不過,結果都是一樣的。
從這裡的執行結果中,Dispose 方法被執行了,執行緒為 1 Dispose 的方法,在主執行緒上運行。
在 Debug 模式下執行的輸出結果
建立 myClassObject物件
MyClass 類別的物件已經被建立了,執行緒為 1
Press any key for continuing...
public void Dispose Ticks=636409966871336607
protected virtual void Dispose Ticks=636409966871341616
Dispose 方法被執行了,執行緒為 1
準備強制進行記憶體回收程序
Ticks=636409966871341616
Press any key for continuing...
-------------------------------
Press any key for continuing...
protected virtual void Dispose Ticks=636409966941140237
Dispose 方法被執行了,執行緒為 2
Destructor Ticks=636409966941140237
MyClass 類別的物件已經被記憶體回收程序釋放了,執行緒為 2
在 Release 模式下執行的輸出結果
建立 myClassObject物件
MyClass 類別的物件已經被建立了,執行緒為 1
Press any key for continuing...
public void Dispose Ticks=636409966350954649
protected virtual void Dispose Ticks=636409966350954649
Dispose 方法被執行了,執行緒為 1
準備強制進行記憶體回收程序
Ticks=636409966350959633
protected virtual void Dispose Ticks=636409966350979684
Dispose 方法被執行了,執行緒為 2
Destructor Ticks=636409966350979684
MyClass 類別的物件已經被記憶體回收程序釋放了,執行緒為 2
Press any key for continuing...
在第三個測試方法中,我們建立完成物件之後,就直接呼叫 Dispose,讓這個方法在主執行緒上運行。
        private static void TestMethod3()
        {
            Console.WriteLine($"建立 myClassObject物件");
            MyClass myClassObject = new MyClass();
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();

            Console.WriteLine($"myClassObject.Dispose();");
            myClassObject.Dispose();
            Console.WriteLine($"準備強制進行記憶體回收程序");
            Console.WriteLine($"Ticks={DateTime.UtcNow.Ticks}");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }

執行的輸出結果
建立 myClassObject物件
MyClass 類別的物件已經被建立了,執行緒為 1
Press any key for continuing...
myClassObject.Dispose();
public void Dispose Ticks=636409971688771288
protected virtual void Dispose Ticks=636409971688776280
Dispose 方法被執行了,執行緒為 1
準備強制進行記憶體回收程序
Ticks=636409971688776280
Press any key for continuing...
-------------------------------
Press any key for continuing...
protected virtual void Dispose Ticks=636409971722019242
Dispose 方法被執行了,執行緒為 2
Destructor Ticks=636409971722019242
MyClass 類別的物件已經被記憶體回收程序釋放了,執行緒為 2

了解更多關於 [實作 Dispose 方法
了解更多關於 [C# 程式設計手冊 





沒有留言:

張貼留言