ファイナライザー (C# プログラミング ガイド)

ガベージ コレクターによってクラス インスタンスが収集されている場合は、ファイナライザー (以前はデストラクターと呼ばれました) を使用して、最終的に必要なすべてのクリーンアップが実行されます。 ほとんどの場合、System.Runtime.InteropServices.SafeHandle または派生クラスを使用してアンマネージド ハンドルをラップすれば、ファイナライザーを記述する必要はありません。

Remarks

  • ファイナライザーは、構造体には定義できません。 クラスでだけ使用します。
  • クラスで使用できるファイナライザーは 1 つだけです。
  • ファイナライザーを継承またはオーバーロードすることはできません。
  • ファイナライザーを呼び出すことはできません。 デストラクターは自動的に起動されます。
  • ファイナライザーは修飾子を取らず、パラメーターはありません。

たとえば、次はクラス 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 メソッドの呼び出しが失敗したときにリソースをクリーンアップするための安全装置になります。

リソースのクリーンアップの詳細については、次の記事を参照してください。

次の例では、継承チェーンを形成する 3 つのクラスを作成します。 First が基底クラスであり、SecondFirst から派生し、ThirdSecond から派生します。 3 つのクラスのいずれにもファイナライザーがあります。 Main では、派生が最も多いクラスのインスタンスが作成されます。 このコードからの出力は、アプリケーションがターゲットとする .NET の実装によって異なります。

  • .NET Framework: 出力は、3 つのクラスのファイナライザーが、アプリケーションの終了時に、派生が最も多いものから派生が最も少ないものの順に自動的に呼び出されることを示しています。
  • .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.
*/

C# 言語仕様

詳細については、C# 言語仕様に関するページの「ファイナライザー」セクションを参照してください。

関連項目