potisanのプログラミングメモ

プログラミング素人です。昔の自分を育ててくれたネット情報に少しでも貢献できるよう、情報を貯めていこうと思っています。Windows環境のC++やC#がメインです。

C# ファイナライザは基本的に呼び出されない

C++のデストラクタの感覚でC#のファイナライザもプログラム終了時に呼び出されると思い込んでいたのですが、ファイナライザは基本的に呼び出されません。呼び出されるのはGCの実行時に対象となった場合のみです。Windowsのプロセス終了時に関連リソースを解放する設計によりほとんどの場合は終了処理の高速化につながりますが、ファイナライザでファイルへの書き込みやExcel COMを閉じる設計だと想定外の動作につながります。

確実な解放処理にはIDisposableIAsyncDisposableが重要だと再確認しました。

具体的には次のコードを実行するとコンストラクタしか呼び出されないことが分かります。nullを代入したりGC.Collectを呼び出したりしても同じです。

var tc1 = new TestClass1();

class TestClass1
{
    public TestClass1()
    {
        Console.WriteLine("TestClass1");
    }

    ~TestClass1()
    {
        Console.WriteLine("~TestClass1");
    }
}

出力

TestClass1

ちなみにサイズの大きなフィールドを持つ場合はprotected void Dispose(bool disposing)の実装とdisposing == true時の参照カウンタ減少 (フィールドへのnullの代入)が有効です。以下のQ&Aにもある通り、大きなフィールドは参照カウントを減らさないとインスタンスがGCに回収されるまでメモリ上に残ってしまいます。