目次

キーワード

概要

ファイルや周辺機器などのリソース(OSが管理している資源)を使用する場合、 まずリソースを使用する権利を取得し、 リソースに対する操作(ファイルの読み書きなど)を行った後、 リソース使用権を破棄する必要があります。

メモリは .NET Framework の「ガーベジコレクション」機能が自動的に管理していて、 プログラマが明示的に破棄してやる必要はないのですが、 ファイルなどは「ガーベジコレクション」の管理対象外で、 明示的な破棄が必要です。

リソースの破棄を怠ると操作が正しく完了しなかったり、 他のプログラムからそのリソースを使用できなくなったりします。 (例えば、ファイルにロックが掛かったままになって、ファイルの読み書きがしばらくできなくなったり。) そのため、リソースの破棄は確実に行う必要があるのですが、 これは意外に面倒な作業だったりします。

ポイント
  • .NET Framework でメモリー管理は自動化されたけど、 管理外のリソース(たとえば、ファイルIO)もある。

  • 管理外のリソースは明示的に破棄が必要。

  • 例外が発生した場合でも正しくリソース破棄ができるように、try-catch-finally や using を使いましょう。

リソース破棄の例

例えば、ファイルの読み書きを行う場合、 まずファイルを開いて、読み書きを行った後、ファイルを閉じる必要があります。 以下に簡単な例を示します。

using System;
using System.IO;

class DisposeTest
{
    static void Main(string[] args)
    {
        FileStream reader = new FileStream(args[0], FileMode.Open);

        // 先頭のNバイトを読み出して画面に表示
        const int N = 32;
        byte[] buf = new byte[N];
        reader.Read(buf, 0, N);
        for (int i = 0; i < N; ++i)
        {
            Console.Write("{0,4}", (int)buf[i]);
            if (i % 8 == 7) Console.Write('\n');
        }

        reader.Close(); // ファイルを閉じる(リソースの破棄)
    }
}

この例のようなリソース破棄の仕方には実は問題があります。 この例のコードでは例外が発生したときに Close メソッドが呼ばれないため、 リソースの開放が出来なくなります。 例外が発生した場合にも Close メソッドが呼ばれるようにするためには、 以下のように try-catch-finally ステートメントを用います

using System;
using System.IO;

class DisposeTest
{
    static void Main(string[] args)
    {
        FileStream reader = new FileStream(args[0], FileMode.Open);

        try
        {
            // 先頭のNバイトを読み出して画面に表示
            const int N = 32;
            byte[] buf = new byte[N];
            reader.Read(buf, 0, N);
            for (int i = 0; i < N; ++i)
            {
                Console.Write("{0,4}", (int)buf[i]);
                if (i % 8 == 7) Console.Write('\n');
            }
        }
        catch (Exception)
        {
            // 例外処理を行う
        }
        finally
        {
            // 例外が発生しようがしまいが finally ブロックは必ず実行される。
            // リソースの破棄は finally ブロックで行う。
            if (reader != null)
                reader.Close();
        }
    }
}

using ステートメント

リソースの破棄の手順をまとめると以下のようになります。 (ただし、Resource はリソース管理用クラスで、 Dispose メソッドによりリソースの破棄を行うものとする。)

Resource r = new Resource();
try
{
  リソースに対する操作
}
finally
{
  if(r != null)
    r.Dispose();
}

リソースの破棄は必ずこの手順で行います (「Dispose パターン」という呼び名もついてる定型パターン)。 しかし、毎回同じ手順を繰り返すのは面倒です。 そこで、C#ではこの手順を自動的に行ってくれる構文が用意されています。 この構文は using ステートメントと呼ばれ、以下のようにして用います。

using(Resource r = new Resource())
{
  リソースに対する操作
}

using ステートメントを用いると、 コンパイラが自動的に上述のリソース破棄用のコードに展開してくれます。 ただし、using ステートメントで使うリソース管理用クラスは System.IDisposable インターフェース を実装している必要があります。 (FileStream などのクラスライブラリ中のクラスは System.IDisposable インターフェースを実装しています。)

using ステートメントを用いて上述の例を書き直したものを以下に示します。

using System;
using System.IO;

class DisposeTest
{
    static void Main(string[] args)
    {
        using (FileStream reader = new FileStream(args[0], FileMode.Open))
        {
            // 先頭のNバイトを読み出して画面に表示

            const int N = 32;
            byte[] buf = new byte[N];
            reader.Read(buf, 0, N);
            for (int i = 0; i < N; ++i)
            {
                Console.Write("{0,4}", (int)buf[i]);
                if (i % 8 == 7) Console.Write('\n');
            }
        }
    }
}

式だけの using ステートメント

ちなみに、using() の中身は変数宣言だけではなく、式にすることもできます。

using()
{
  リソースに対する操作
}

これで、以下のようなコードと同等な処理になります。

using(IDisposable r = )
{
  リソースに対する操作
}

さらに展開すると、以下のような意味です。

Resource r = ;
try
{
  リソースに対する操作
}
finally
{
  if(r != null)
    r.Dispose();
}

用途としては例えば、以下の「ジェネリック」を使ったメソッドのように、 T が IDispose を実装している時だけ Dispose を呼び出したい場合などに便利です。

static void GenericMethod<T>(T obj)
{
    using (obj as IDisposable)
    {
       obj に対する操作
    }
}

更新履歴

ブログ