bucket-sort logo bucket-sort

プログラミングとインフラエンジニアリングの覚え書き

  • Posts
  • About
  • Contact
  1. Home
  2. All Posts
  3. [C#] Finalizable & Disposable パターン実践 — Dispose パターンの完全形

[C#] Finalizable & Disposable パターン実践 — Dispose パターンの完全形

May 13, 2026 C# , .NET bucket-sort

Finalizable ObjectsとDisposable Objectsで、Finalize() と Dispose() それぞれの役割を整理しました。実際のコードでは、両者を組み合わせた 「Dispose パターン」 として使うのが定石です。

この記事では、状況に応じた 4 つのパターンを 動くサンプルコード で示し、それぞれの「書き方の意図」を解説します。

パターン 1:マネージドのみのシンプルな Disposable

最も基本的なケースです。アンマネージドリソースを直接持たず、IDisposable フィールドだけを持つ場合、ファイナライザは不要です。

public sealed class LogWriter : IDisposable
{
    private readonly StreamWriter _writer;
    private bool _disposed;

    public LogWriter(string path)
    {
        _writer = new StreamWriter(path, append: true);
    }

    public void WriteLine(string message)
    {
        ThrowIfDisposed();
        _writer.WriteLine($"{DateTime.UtcNow:O} {message}");
    }

    public void Dispose()
    {
        if (_disposed) return;

        _writer.Dispose();
        _disposed = true;
    }

    private void ThrowIfDisposed()
    {
        if (_disposed) throw new ObjectDisposedException(nameof(LogWriter));
    }
}

ポイント

  • sealed にすることで、継承による Dispose の挙動の乱れを防ぐ
  • _disposed フラグ で多重 Dispose を安全に
  • ファイナライザは書かない:StreamWriter 自身が責任を持つので二重管理しない
  • 使用後は ObjectDisposedException を投げる

このパターンが 最も多く書くケース です。「アンマネージドリソース=持っていない」と確信できるなら、ファイナライザを書く理由はありません。

using var log = new LogWriter("app.log");
log.WriteLine("Hello, world!");

パターン 2:SafeHandle を使うアンマネージド対応(推奨)

アンマネージドリソース(OS ハンドルなど)を扱う場合、自前でファイナライザを書くより SafeHandle を使う のが現代の推奨です。SafeHandle 自身が CriticalFinalizerObject を継承しており、確実な解放と安全性が保証されています。

using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;

internal sealed class MyFileHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    public MyFileHandle() : base(ownsHandle: true) { }

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr handle);

    protected override bool ReleaseHandle()
    {
        return CloseHandle(handle);
    }
}

public sealed class NativeFile : IDisposable
{
    private readonly MyFileHandle _handle;
    private bool _disposed;

    public NativeFile(string path)
    {
        _handle = NativeMethods.OpenFile(path); // SafeHandle を返す P/Invoke
    }

    public void Dispose()
    {
        if (_disposed) return;

        _handle.Dispose(); // SafeHandle が確実に閉じる
        _disposed = true;
    }
}

ポイント

  • NativeFile 自身にファイナライザは不要
  • SafeHandle がファイナライザを内蔵しているので、Dispose 忘れにも対応
  • アンマネージドリソースを持つ型は SafeHandle でラップするのが最初の選択肢

SafeHandle の詳細も参照してください。

パターン 3:継承を考慮した完全な Dispose パターン

SafeHandle が使えない、あるいは派生クラスでも追加リソースを解放したい——そんなときに使う、「教科書通り」の Dispose パターン です。protected virtual Dispose(bool disposing) を中心とした古典的な構造になります。

public class ResourceHolder : IDisposable
{
    // アンマネージド
    private IntPtr _nativePtr;
    // マネージド
    private FileStream? _stream;
    private bool _disposed;

    public ResourceHolder(string path, int nativeSize)
    {
        _nativePtr = Marshal.AllocHGlobal(nativeSize);
        _stream = new FileStream(path, FileMode.OpenOrCreate);
    }

    // 派生クラスはここをオーバーライドして拡張する
    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;

        if (disposing)
        {
            // マネージドリソース:Dispose() 経由のときだけ触れる
            _stream?.Dispose();
            _stream = null;
        }

        // アンマネージドリソース:ファイナライザ経由でも安全に触れる
        if (_nativePtr != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(_nativePtr);
            _nativePtr = IntPtr.Zero;
        }

        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this); // ファイナライザを抑制
    }

    ~ResourceHolder()
    {
        Dispose(disposing: false);
    }
}

disposing パラメータの意味

値 呼び出し元 できること
true Dispose() から マネージド・アンマネージド両方
false ファイナライザから アンマネージドのみ

ファイナライザ経由(disposing == false)では、マネージドフィールドが既にファイナライズされている可能性がある ため、それらに触れてはいけません。これが disposing フラグの存在理由です。

GC.SuppressFinalize(this) の意味

Dispose() で正しく解放されたなら、ファイナライザを呼ぶ必要はありません。GC.SuppressFinalize(this) を呼ぶことで、

  • ファイナライズキューから外される
  • 世代昇格が起きない
  • GC 負荷が下がる

という効果が得られます。Dispose() の最後に必ず書く のが定石です。

派生クラスの書き方

public sealed class ExtendedHolder : ResourceHolder
{
    private readonly Timer _timer;

    public ExtendedHolder(string path, int size, TimerCallback cb)
        : base(path, size)
    {
        _timer = new Timer(cb);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _timer.Dispose();
        }
        base.Dispose(disposing); // 必ず親に委譲
    }
}

派生側は Dispose(bool) をオーバーライドし、最後に base.Dispose(disposing) を必ず呼ぶ のがルールです。

パターン 4:IAsyncDisposable も実装する

非同期で解放処理を行うリソース(DB 接続、ネットワークソケット、バッファのフラッシュなど)では、IAsyncDisposable も併せて実装します。

public sealed class AsyncBuffer : IDisposable, IAsyncDisposable
{
    private readonly Stream _stream;
    private bool _disposed;

    public AsyncBuffer(Stream stream) => _stream = stream;

    public void Dispose()
    {
        if (_disposed) return;
        _stream.Dispose();
        _disposed = true;
    }

    public async ValueTask DisposeAsync()
    {
        if (_disposed) return;
        await _stream.DisposeAsync().ConfigureAwait(false);
        _disposed = true;
        GC.SuppressFinalize(this);
    }
}

使い方は await using です。

public async Task UseAsync()
{
    await using var buffer = new AsyncBuffer(stream);
    // ...
} // ここで await buffer.DisposeAsync()

両方を実装したクラスでは、非同期コンテキストでは await using を優先、同期コンテキストでは using を使う、と使い分けます。

どのパターンを選ぶか

実務での選択フローは次のとおりです。

アンマネージドリソースを直接持つ?
├── No → パターン 1(シンプル)
└── Yes
    ├── SafeHandle でラップできる?
    │   ├── Yes → パターン 2(SafeHandle、推奨)
    │   └── No  → パターン 3(完全パターン)
    └── 非同期解放が必要?
        └── Yes → パターン 4(IAsyncDisposable 併用)

ほとんどのケースはパターン 1 か 2 で済みます。 パターン 3 は本当にアンマネージドを直接扱うフレームワーク・ライブラリ層で書くもので、アプリケーションコードで頻出するわけではありません。

チェックリスト

最後に、Dispose パターンを書くときの自己チェック項目です。

  • 多重 Dispose() を防ぐフラグはあるか
  • Dispose() 後の使用で ObjectDisposedException を投げているか
  • ファイナライザを書いたなら GC.SuppressFinalize(this) を呼んでいるか
  • Dispose(false) ではマネージドフィールドに触れていないか
  • 派生クラスは base.Dispose(disposing) を呼んでいるか
  • アンマネージドを SafeHandle で置き換えられないか検討したか
  • 非同期解放が必要なら IAsyncDisposable を実装したか

Finalize と Dispose を正しく組み合わせれば、リソース解放は 「保険」と「正規ルート」 の二段構えで安全になります。次の記事では、こうしたリソース管理とは別軸の最適化テクニックである Lazy Object Instantiation(遅延初期化) を扱います。

C# .NET IDisposable Dispose Finalize SafeHandle デザインパターン
← [C#] Disposable Objects — IDisposable / Dispose() と using 構文

Related Posts

  • [C#] Disposable Objects — IDisposable / Dispose() と using 構文 May 12, 2026
  • [C#] Finalizable Objects — Finalize() の役割と使いどころ May 11, 2026
  • [C#] struct を深掘りする — 基本・コンストラクタ・フィールド初期化・readonly・ref struct・IDisposable Apr 14, 2026
  • [C#] delegate と event の仕組みを整理する Apr 1, 2026

Table of Contents

  • パターン 1:マネージドのみのシンプルな Disposable
    • ポイント
  • パターン 2:SafeHandle を使うアンマネージド対応(推奨)
    • ポイント
  • パターン 3:継承を考慮した完全な Dispose パターン
    • disposing パラメータの意味
    • GC.SuppressFinalize(this) の意味
    • 派生クラスの書き方
  • パターン 4:IAsyncDisposable も実装する
  • どのパターンを選ぶか
  • チェックリスト

Recent Posts

  • [C#] Finalizable & Disposable パターン実践 — Dispose パターンの完全形 May 13, 2026
  • [C#] Disposable Objects — IDisposable / Dispose() と using 構文 May 12, 2026
  • [C#] Finalizable Objects — Finalize() の役割と使いどころ May 11, 2026
  • [C#] System.GC クラスを整理する — ガベージコレクションを制御するための API May 10, 2026
  • [C#] IComparable と IComparer — オブジェクトの順序比較と複数ソート戦略 May 9, 2026

Categories

  • C#63
  • .NET62
  • AWS27
  • Laravel16
  • Linux15
  • MySQL9
  • Apache8
  • PHP8
  • DynamoDB6
  • セキュリティ6
  • Nginx5
  • WordPress4
  • インフラ4
  • Hugo3
  • .NET Framework1
  • Aurora1
  • Filament1
  • Git1
  • SQS1

Tags

  • C#
  • .NET
  • AWS
  • Laravel
  • PHP
  • セキュリティ
  • MySQL
  • Linux
  • Apache
  • Code Snippet
  • DynamoDB
  • NoSQL
  • PHP-FPM
  • RDS
  • パフォーマンス
  • DoS
  • Nginx
  • Windows
  • WordPress
  • メモリ管理
  • 監視
  • 設計
  • Amazon Linux 2023
  • Docker
  • IDisposable
  • Ipset
  • Iptables
  • OPCache
  • Webサーバー
  • オブジェクト指向
  • クラス設計
  • コレクション
  • デザインパターン
  • パターンマッチング
  • 継承
  • 認可
  • Aurora
  • Blade
  • Grafana
  • Hugo
  • InfluxDB
  • Policy
  • Record
  • SSG
  • インターフェース
  • エラーハンドリング
  • カプセル化
  • ガベージコレクション
  • モニタリング
  • 例外
Powered by Hugo & Explore Theme.