【C#】usingステートメントでリソースを確実に解放する方法とIDisposable

ファイル操作やデータベース接続など、外部リソース(アンマネージドリソース)を扱うプログラムにおいて、使用後の「後始末」は極めて重要です。リソースを開放(Dispose)し忘れると、ファイルがロックされたままになったり、メモリリークを引き起こしたりする原因となります。

C#では、IDisposableインターフェースを実装したオブジェクトに対してusingステートメントを使用することで、例外発生時であっても確実にDisposeメソッドを呼び出すことができます。ここでは、ファイルへのログ書き込み処理を題材に、usingステートメントの仕組みと、C# 8.0で導入されたモダンな記述方法について解説します。


目次

usingステートメントの役割と仕組み

usingステートメントは、コンパイル時に自動的にtry-finallyブロックへと展開される「糖衣構文(Syntactic Sugar)」です。

開発者が手動でfinallyブロックを記述し、その中でDisposeメソッドを呼び出すコードを書くことも可能ですが、記述が冗長になり、呼び出し忘れのリスクも伴います。usingを使用することで、スコープを抜けたタイミングで自動的にリソース解放が行われることが保証されます。

比較:手動による解放とusingによる解放

以下の2つのコードは、コンパイル後の動作としてはほぼ等価です。

1. try-finallyを使用した冗長な記述(非推奨)

例外処理とリソース解放が混在し、可読性が低下します。また、writer変数のnullチェックなども考慮する必要があります。

var writer = new System.IO.StreamWriter("log.txt");
try
{
    writer.WriteLine("処理を開始しました");
}
finally
{
    // 明示的にDisposeを呼ばなければならない
    if (writer != null)
    {
        ((IDisposable)writer).Dispose();
    }
}

2. usingステートメントを使用した記述(推奨)

簡潔であり、リソースの有効範囲(スコープ)が明確になります。

using (var writer = new System.IO.StreamWriter("log.txt"))
{
    writer.WriteLine("処理を開始しました");
} // ここで自動的にDispose()が呼ばれる

実践的なコード例:ログファイルへの書き込み

ここでは、C# 8.0で導入された「using宣言(using declaration)」を使用した、よりモダンで簡潔な記述方法を紹介します。

ネスト(入れ子)が深くならず、変数のスコープがブロックの末尾まで続くため、コード全体がフラットで読みやすくなります。

using System;
using System.IO;
using System.Text;

namespace LoggerApplication
{
    class Program
    {
        static void Main()
        {
            string logFilePath = "application.log";

            try
            {
                WriteLog(logFilePath, "[Info] アプリケーションを起動しました。");
                WriteLog(logFilePath, "[Info] データの読み込み完了。");
                
                Console.WriteLine("ログの書き込みが完了しました。");
            }
            catch (IOException ex)
            {
                Console.Error.WriteLine($"ファイル操作エラー: {ex.Message}");
            }
        }

        /// <summary>
        /// 指定されたファイルにログメッセージを追記します。
        /// </summary>
        static void WriteLog(string path, string message)
        {
            // C# 8.0以降の「using宣言」
            // ブロック { } を使わず、変数の宣言時に using を付けます。
            // このメソッド(WriteLog)を抜ける際に、自動的にDisposeが呼ばれます。
            using var writer = new StreamWriter(path, append: true, Encoding.UTF8);
            
            string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            writer.WriteLine($"{timestamp} {message}");
            
            // 明示的な Close() や Dispose() の呼び出しは不要です。
        }
    }
}

実行結果(application.log の内容)

2025-10-25 10:00:00 [Info] アプリケーションを起動しました。
2025-10-25 10:00:01 [Info] データの読み込み完了。

技術的なポイントとIDisposable

1. IDisposableインターフェースの実装

usingステートメントの対象にできるのは、System.IDisposableインターフェースを実装しているクラスのみです。このインターフェースには単一のメソッドvoid Dispose()が定義されています。

自作クラスでリソース管理(ファイルハンドルやデータベース接続など)を行う場合は、必ずこのインターフェースを実装し、利用者がusingを使えるように設計する必要があります。

2. DisposeとCloseの違い

StreamWriterSqlConnectionなどのクラスには、DisposeメソッドのほかにCloseメソッドが存在することがあります。多くの場合、これらは機能的に同等であり、Closeの内部でDisposeが呼ばれる実装になっています。 しかし、.NETの標準的なリソース解放の作法としては、IDisposable経由のDispose(つまりusingの使用)が推奨されます。

3. 非同期Dispose (IAsyncDisposable)

C# 8.0からは、非同期でリソース解放を行うためのIAsyncDisposableインターフェースと、await using構文が導入されました。ファイルI/Oやネットワーク通信など、解放処理自体にI/O待ちが発生する可能性がある場合は、こちらを使用することでスレッドをブロックせずにリソースを破棄できます。

// 非同期でのリソース解放
await using var writer = new StreamWriter("log.txt");
await writer.WriteLineAsync("Log message");

まとめ

usingステートメントは、リソースリークを防ぎ、堅牢なアプリケーションを構築するための基本かつ最重要な機能の一つです。特にC# 8.0以降の「using宣言」を活用することで、コードの可読性を損なうことなく安全なリソース管理が可能になります。IDisposableを実装するクラスを扱う際は、必ずusingを使用する習慣をつけてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次