デストラクタ

デストラクタの要件

  • クラス以外では、定義できない
  • 1つのクラスでは、複数の定義はできない
  • 継承やオーバーロードはできない
  • 明示的に呼び出せない
  • 修飾子や引数を持てない
Remarks - Finalizers - C# Programming Guide | Microsoft Learn

ファイナライザ (finalizer)

class Example
{
    ~Example() // デストラクタ
    {
        // 終了処理
    }
}

デストラクタの構文は、実際にはFinalize()の呼び出しとなります。

protected override void Finalize() // ファイナライザ
{
    try
    {
        // 終了処理
    }
    finally
    {
        base.Finalize();
    }
}

Dispose()の実装

ファイナライザに頼らない方が良い理由。

  • ファイナライザが呼び出されるタイミングは制御できず、呼び出されないこともある。
  • 複数のオブジェクトのファイナライザが呼び出される順番を予測できない。
  • ファイナライザを持つオブジェクトがほかのオブジェクトを参照している場合、参照先のオブジェクトの寿命が不要に引き延ばされる。
  • ファイナライザを持つオブジェクトは、マネージド ヒープに割り当てるときに時間がかかる。
C#エッセンシャルズ 第2版 3.12.3「ファイナライザ」

よって終了処理が必須ならば、IDisposableインターフェイスを継承しDispose()を実装します。

サンプルコード

class Example : System.IDisposable
{
    private bool disposed = false; // Dispose(bool)がくり返し呼ばれた場合に対処するためのフラグ

    // デストラクタ
    // Dispose()が呼ばれなかった場合のため。解放するアンマネージ リソースがないならば不要
    ~Example()
    {
        Dispose( false ); // アンマネージ リソースのみを解放する
    }

    public void Dispose()
    {
        Dispose( true ); // マネージとアンマネージ リソースを解放する

        // このインスタンスのファイナライザが呼び出されないようにする
        // ファイナライザ (デストラクタ) をオーバーライドしないならば不要
        System.GC.SuppressFinalize( this );
    }

    protected virtual void Dispose( bool disposing )
    {
        if( !disposed )
        {
            if( disposing )
            {
                // マネージド リソースを解放する
                // 引数なしのDispose()から呼ばれたときに実行する処理であり、デストラクタから呼ばれたときは処理しない
            }

            // アンマネージド リソースを解放する

            disposed = true;
        }
    }
}
IDisposable インターフェイス (System) | Microsoft Learn GC.SuppressFinalize(Object) メソッド (System) | Microsoft Learn

ファイナライザー スレッド (finalizer thread)

ファイナライザを呼び出す前にCLRがシャットダウンされると、ファイナライザー スレッドが開始され、そこでファイナライザが呼ばれます。Remarks - Environment.HasShutdownStarted Property (System) | Microsoft Learn

ガベージコレクション (Garbage Collection)

Dispose()メソッド

明示的にリソースを解放する必要があるならば、IDisposableインターフェイスを実装します。そしてそのインターフェイスを実装しているクラスのオブジェクトは、Dispose()メソッドにより解放できます。IDisposable Interface (System) | Microsoft Learn

void Dispose()
IDisposable.Dispose Method (System) | Microsoft Learn

IDisposableを実装しているオブジェクトがスコープ内で破棄されない可能性があることは、コード分析CA2000で検出できます。

usingステートメント (using Statement)

usingの対象とできる型は、IDisposableを継承したクラスだけです。Compiler Error CS1674 | MSDN

using (FileStream fs = new FileStream("sample.txt", FileMode.Create))
{
    // オブジェクトを使用した処理
}

usingステートメントはDispose()を確実に呼び出すための簡易構文に過ぎず、コンパイル時には次のように展開されます。

{
    FileStream fs = new FileStream("sample.txt", FileMode.Create);
    try
    {
        // オブジェクトを使用した処理
    }
    finally
    {
        if (fs != null)
        {
            ((IDisposable)fs).Dispose();
        }
    }
}

ネストされたusing宣言

ネストされたusing宣言は、

using (ResourceType r1 = e1)
{
    using (ResourceType r2 = e2)
    {
    }
}

次のようにも記述できます。

using (ResourceType r1 = e1)
using (ResourceType r2 = e2)
{
}

このときインスタンスのクラスが同一ならば、カンマ区切りで並べて記述できます。

using (ResourceType r1 = e1, r2 = e2)
{
}

入れ子の内側のusingステートメントのリソースに、外側のusingステートメントのリソースを格納する形式となる場合には、usingでネストせずtry-finallyで記述するようにします。このようにネストすると同一のリソースがくり返し破棄される恐れがあるため、コード分析で「オブジェクトを複数回破棄しません」としてCA2202で警告されます。

using宣言 (using declaration)

C# 8.0以降ならば、usingステートメントの中かっこを省略して記述できます。using declaration - Pattern based using and using declarations - C# 8.0 draft feature specifications | Microsoft Learn

つまり、

{
    using (FileStream f = new FileStream("sample.txt"))
    {
        // オブジェクトを使用した処理
    }
}

のような記述は次のようにかっこを省略でき、オブジェクトの寿命はそれが宣言されているスコープの最後までとなります。

{
    using FileStream f = new FileStream("sample.txt");
    // オブジェクトを使用した処理
}

参考

参考書

オブジェクトの破棄

基本的にガベージ コレクタが自動で破棄するため、明示的な処理は不要です。しかし必要ならば、Dispose()を呼びます。

obj.Dispose();

もし対象のオブジェクトがIDisposableを実装しておらず、Dispose()を呼べないならば

obj = null;

とします。

トラブル対処法

CA2213

コード分析の実行により、「'A' は、IDisposable 型 'B' であるフィールド 'A.B' を含んでいます。このフィールドで Dispose または Close を呼び出すには、'A' の Dispose メソッドを変更してください。」と警告されるときには、クラスAのDispose()内で、フィールドBのDispose()を呼び出すようにします。

この警告はIDisposableを継承したクラスが、同様にIDisposableを継承したクラスをフィールドに持つとき、Dispose()メソッドでフィールドのDispose()を呼び出していない場合に発生します。CA2213: 破棄可能なフィールドは破棄されなければなりません | MSDN

メモリの使用量が大きい

適切に解放されているか、ツールを用いて確認します。≫ プロファイリング ツール

Microsoft Learnから検索