完成項 (C# 程式設計手冊)
完成項 (舊稱為解構函式) 可在記憶體回收行程收集類別執行個體時,用來執行任何必要的最後清除。 在大部分情況下,您可以使用 System.Runtime.InteropServices.SafeHandle 或衍生類別來包裝任何不受控的控制代碼,以避免撰寫完成項。
備註
- 無法在結構中定義完成項。 它們只能與類別搭配使用。
- 一個類別只能有一個完成項。
- 無法繼承或多載完成項。
- 無法呼叫完成項。 會自動呼叫它們。
- 完成項不會接受修飾詞,也不會包含參數。
例如,下列是 Car
類別的完成項宣告。
class Car
{
~Car() // finalizer
{
// cleanup statements...
}
}
完成項也可以實作為運算式主體定義,如下列範例所示。
public class Destroyer
{
public override string ToString() => GetType().Name;
~Destroyer() => Console.WriteLine($"The {ToString()} finalizer is executing.");
}
完成項會在物件的基底類別上隱含地呼叫 Finalize。 因此,會將完成項呼叫隱含地轉譯為下列程式碼︰
protected override void Finalize()
{
try
{
// Cleanup statements...
}
finally
{
base.Finalize();
}
}
此設計表示,會依最高衍生性到最低衍生性的順序,對繼承鏈結中的所有執行個體遞迴呼叫 Finalize
方法。
注意
不應該使用空的完成項。 類別包含完成項時,會在 Finalize
佇列中建立一個項目。 此佇列會由記憶體回收行程處理。 當 GC 處理佇列時,它會呼叫每一個完成項。 不必要的完成項,包括空的完成項、只呼叫基底類別完成項的完成項,或只呼叫條件式發出方法的完成項,會導致不必要的效能遺失。
程式設計人員無法控制完成項的呼叫時機;而是由記憶體回收行程決定呼叫的時機。 記憶體回收行程會檢查應用程式不再使用的物件。 如果它認為物件適合進行完成,則會呼叫完成項 (如果有的話),並回收用來儲存物件的記憶體。 呼叫 Collect 可能會強制執行記憶體回收,但在大部分的情況下,此呼叫可能會造成效能問題,因此應該予以避免。
注意
是否要在應用程式終止過程中執行完成項,取決於每一個 .NET 實作。 當應用程式終止時,.NET Framework 會盡可能在合理的情況下,針對尚未回收記憶體的物件呼叫完成項,除非這類清除遭到抑制 (例如,藉由呼叫程式庫方法 GC.SuppressFinalize
)。 .NET 5 (包括 .NET Core) 和更新版本不會在應用程式終止過程中呼叫完成項。 如需詳細資訊,請參閱 GitHub 問題 dotnet/csharpstandard #291。
如果您需要在應用程式結束時以可靠方式執行清除,請註冊 System.AppDomain.ProcessExit 事件的處理常式。 該處理常式可確保已針對需要在應用程式結束前清除的所有物件呼叫 IDisposable.Dispose() (或 IAsyncDisposable.DisposeAsync())。 因為您無法直接呼叫 Finalize,而且無法保證記憶體回收行程會在結束前呼叫所有完成項,所以您必須使用 Dispose
或 DisposeAsync
以確保資源已釋放。
使用完成項來釋放資源
相較於不是以執行階段的記憶體回收為目標的語言,C# 通常並不需要開發人員進行太多的記憶體管理。 這是因為 .NET 記憶體回收行程會隱含地管理您物件的記憶體配置和釋放。 不過,您的應用程式封裝視窗、檔案和網路連線這類未受管理資源時,應該使用完成項來釋放這些資源。 適合完成物件時,記憶體回收行程會執行物件的 Finalize
方法。
明確釋放資源
如果您的應用程式使用過多的外部資源,則也建議您提供一種方式,以在記憶體回收行程釋放物件之前明確釋放資源。 若要釋放資源,請 IDisposable 介面實作 Dispose
方法,以便對物件執行必要清除。 這可以大幅改善應用程式效能。 即使對資源使用這個明確控制,如果 Dispose
方法呼叫失敗,完成項還是會成為清除資源的保護措施。
如需清除資源的詳細資訊,請參閱下列文章:
範例
下列範例會建立三個產生繼承鏈結的類別。 First
類別是基底類別、Second
衍生自 First
,而 Third
衍生自 Second
。 所有這三個都有完成項。 在 Main
中,會建立最高衍生性類別的執行個體。 此程式碼的輸出取決於應用程式設為目標的 .NET 實作:
- .NET Framework:輸出顯示當應用程式終止時,會依照最高衍生性到最低衍生性順序,自動呼叫這三個類別的完成項。
- .NET 5 (包括 .NET Core) 或更新版本:沒有輸出,因為這個 .NET 實作不會在應用程式終止時呼叫完成項。
class First
{
~First()
{
System.Diagnostics.Trace.WriteLine("First's finalizer is called.");
}
}
class Second : First
{
~Second()
{
System.Diagnostics.Trace.WriteLine("Second's finalizer is called.");
}
}
class Third : Second
{
~Third()
{
System.Diagnostics.Trace.WriteLine("Third's finalizer is called.");
}
}
/*
Test with code like the following:
Third t = new Third();
t = null;
When objects are finalized, the output would be:
Third's finalizer is called.
Second's finalizer is called.
First's finalizer is called.
*/