プログラマとプロマネのあいだ

プログラマもやるし、プロマネもやるし、たまに似非アーキとか営業っぽいこともやる

IDisposableとusing文

プログラミングC# p86〜87

IDisposableを実装したクラスをusing文と共に作成すると、using文のブロックの終わりで自動的にDisposeメソッドが呼び出されます。
Rubyでいうところの、open関数のブロックみたいなものか?

using System;

class DisposeTest : IDisposable
{
        public void hello()
        {
                Console.WriteLine("DisposeTest#hello");
        }

        public virtual void Dispose()
        {
                Console.WriteLine("DisposeTest#Dispose");
        }
}

class TestDriver
{
        static void Main(string[] args)
        {
                using (DisposeTest d = new DisposeTest())
                {
                        d.hello();
                }
        }
}

※書籍中では、protected virtual void Dispose(bool disposing)という宣言になってますが、現在はpublicで引数無しが正しいようです。
この結果はこう。

DisposeTest#hello
DisposeTest#Dispose

というわけで、Dispose呼ばれてますね。
じゃあデストラクタとの兼ね合いはどうか。
デストラクタが呼ばれた場合でも、Disposeが呼ばれた場合でも、なんらかの後始末を行いたいとします。で、こんなコードを書いてみる。

using System;

class DisposeTest : IDisposable
{
        public void hello()
        {
                Console.WriteLine("DisposeTest#hello");
        }

        public virtual void Dispose()
        {
                Console.WriteLine("DisposeTest#Dispose");
                destroyProcess();
        }

        ~DisposeTest()
        {
                Console.WriteLine("~DisposeTest");
                destroyProcess();
        }

        private void destroyProcess()
        {
                // なんらかの後始末処理
                Console.WriteLine("DisposeTest#destroyProcess");
        }
}

class TestDriver
{
        static void Main(string[] args)
        {
                using (DisposeTest d = new DisposeTest())
                {
                        d.hello();
                }
        }
}

この結果はこう。

DisposeTest#hello
DisposeTest#Dispose
DisposeTest#destroyProcess
~DisposeTest
DisposeTest#destroyProcess

ありゃりゃ。DisposeTest#destroyProcessが二回呼ばれちゃってますね。
これを回避するのが、GC.SuppressFinalize(this);というおまじない。
これはこのメソッドが呼ばれた場合は、デストラクタを呼ばないようにするというワザ(?)です。

using System;

class DisposeTest : IDisposable
{
        public void hello()
        {
                Console.WriteLine("DisposeTest#hello");
        }

        public virtual void Dispose()
        {
                Console.WriteLine("DisposeTest#Dispose");
                destroyProcess();

                GC.SuppressFinalize(this);
        }

        ~DisposeTest()
        {
                Console.WriteLine("~DisposeTest");
                destroyProcess();
        }

        private void destroyProcess()
        {
                // なんらかの後始末処理
                Console.WriteLine("DisposeTest#destroyProcess");
        }
}

class TestDriver
{
        static void Main(string[] args)
        {
                using (DisposeTest d = new DisposeTest())
                {
                        d.hello();
                }
        }
}

この結果はこう。

DisposeTest#hello
DisposeTest#Dispose
DisposeTest#destroyProcess

期待通りですね。