none
IDisposableの実装の仕方について教えてください RRS feed

  • 質問

  • いつもお世話になっております。

    C#でプログラムを組んでおります。
    IDisposableをクラスに実装しようと思っているのですが、よくわからなかったので質問させてください。

    まずMSDNライブラリの以下のところを参考にしたのですが、

    public void Dispose()
        {
          Dispose(true);
          // This object will be cleaned up by the Dispose method.
          // Therefore, you should call GC.SupressFinalize to
          // take this object off the finalization queue 
          // and prevent finalization code for this object
          // from executing a second time.
          GC.SuppressFinalize(this);
        }
    
        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed.
        private void Dispose(bool disposing)
        {
          // Check to see if Dispose has already been called.
          if(!this.disposed)
          {
            // If disposing equals true, dispose all managed 
            // and unmanaged resources.
            if(disposing)
            {
            // Dispose managed resources.
            component.Dispose();
            }
      		 
            // Call the appropriate methods to clean up 
            // unmanaged resources here.
            // If disposing is false, 
            // only the following code is executed.
            CloseHandle(handle);
            handle = IntPtr.Zero;			
          }
          disposed = true;     
        }
    
    

    1点目ですが、Disposeメソッド内にマネージリソースの解放をするところがあるようですが(component.Disposeのところ)、これはそのクラス内で宣言したメンバー変数についてはすべてこのクラスのDisposeメソッド内に、変数の解放処理(変数ごとのDispose)を書かなければいけないのでしょうか。

    例えば
    sqlconnection scn;
    sqldataadapter sda;
    sqlcommandbuilder scm;
    を宣言していた場合、        
    
            if(disposing)
            {
            // Dispose managed resources.
            scn.Dispose();
            sda.Dispose();
            scm.Dispose();
            }
    
    と、宣言した変数すべて解放する記述をしなければならないのでしょうか。

    上記のような例の場合、個人的にはscnだけ解放すればよいような気がするのですが。
    また、int型など、オブジェクトじゃないものはどうしたらいいでしょうか。

    2点目ですが、GC.SuppressFinalize(this)←この文はファイナライズを実行しないということらしいですが、
    ということは、呼び出し元でこのクラスのインスタンスのDisposeを呼んでも、このクラスのインスタンスはプログラムが終了するまで破棄されないということでしょうか。
    (このクラスのインスタンス内のメンバー変数については、1点目の質問にあるようにしなければならないなら、全部解放されるのかもしれませんが)

    勉強不足もありまとはずれな部分もあるかと思いますが、よろしくお願いします。

    2010年7月13日 12:27

回答

  • 1点目ですが、Disposeメソッド内にマネージリソースの解放をするところがあるようですが(component.Disposeのところ)、これはそのクラス内で宣言したメンバー変数についてはすべてこのクラスのDisposeメソッド内に、変数の解放処理(変数ごとのDispose)を書かなければいけないのでしょうか。

    必要なものは Dispose すべきです。

    上記のような例の場合、個人的にはscnだけ解放すればよいような気がするのですが。

    前述のように必要なものは Dispose すべきです。
    提示されている例がどうかは確認していませんが、親のオブジェクトを Dispose すれば子オブジェクトはすべて Dispose されるのであれば、親のオブジェクトだけで良い場合もあります。

    また、int型など、オブジェクトじゃないものはどうしたらいいでしょうか。

    int 型は Dispose メソッドを実装していませんので不要でしょう。
    Dispose を実装するべきは、すぐさまに解放などが必要なものを持つクラスです。(ファイルハンドルとか、アンマネージメモリとか、またはそれらを持つクラスのオブジェクトとか)

    2点目ですが、GC.SuppressFinalize(this)←この文はファイナライズを実行しないということらしいですが、
    ということは、呼び出し元でこのクラスのインスタンスのDisposeを呼んでも、このクラスのインスタンスはプログラムが終了するまで破棄されないということでしょうか。

    Dispose とマネージメモリの回収(ガベージコレクション = GC)は別物です。

    ファイナライザを実装するクラスのオブジェクトは、GC で回収対象になった後も、即座に回収されません。
    ファイナライザの処理が終わった後のどこかのタイミングで回収されることになるため、回収が遅くなります。
    Dispose であらかじめアンマネージメモリなどの解放処理をしており、ファイナライザを呼び出す必要がないことをシステムに通知する(GC.SuppressFinalize)ことで、その回収が遅くなることを防ぐことができます。

    GC などに関しては以下のページを読んでみてください。
    http://msdn.microsoft.com/ja-jp/library/bb985010.aspx
    http://msdn.microsoft.com/ja-jp/library/dd297765.aspx


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年7月13日 14:00
    モデレータ
  • 2010年7月13日 22:34
  • もし、そのクラスで宣言しているメンバ変数がすぐに解放すべきものかどうかわからなければ、
    最悪全部のメンバ変数についてDisposeメソッド内で解放(Dispose)すればよいということでよろしいでしょうか。
    (その変数のクラスが解放が必要か調べて必要なものだけDisposeを書くのが一番よいのでしょうが)

    外池と申します。

    結論から言えば、その通りだと思います。詳細は下記の2)をご覧下さい。

    おそらくハッシュドビーフさんの頭の中では既に整理がついているようにお見受けしますが、念のため、2つの事柄をわけて書いておきますと・・・。

    1)あるクラスのオブジェクトが、Disposeメソッドを使って解放操作をしてやるべきものかどうかは、当たり前のような話ですが、Disposeメソッドがあるかどうかで判断できます。DisposeメソッドがあるオブジェクトはDisposeしてやりましょう。逆に、そのようなオブジェクトをメンバ変数で内包しているクラスをご自身で書くときには、メンバ変数をどこかでDisposeするように書いてやらなければならず、場合によってはDisposeメソッドを実装することになります。

    これは、比較的簡単に原則論を書けます。ややこしいのは・・・、

    2) 他の人が書いたクラスのオブジェクト同士が相互に関連しあっていて、あるオブジェクトをDisposeすれば他のオブジェクトも連動してDisposeするようになっている場合、判りにくいことは多々あると思います。本当は、そのクラスのドキュメントをしっかり読めば、判るようになっていないとダメなのですが、そうも言ってられません。とにかく、Disposeできるオブジェクトは、最後にまとめて全部Disposeしてやるように書いておくのが精神衛生上良いかと思います。

    .Net Frameworkの場合は、Disposeし忘れたままあるオブジェクトへの参照がすべてスコープ外になってしまっても、そのうちGC時にDisposeしてもらえる・・・、という安全な構造になっているから、適当でいいんだよ、なんて乱暴な見解もあり得ますが、パフォーマンスを良くする観点から、DisposeできるときにDisposeしておくことが原則と思います。


    (ホームページを再開しました)
    2010年7月19日 8:01

すべての返信

  • 1点目ですが、Disposeメソッド内にマネージリソースの解放をするところがあるようですが(component.Disposeのところ)、これはそのクラス内で宣言したメンバー変数についてはすべてこのクラスのDisposeメソッド内に、変数の解放処理(変数ごとのDispose)を書かなければいけないのでしょうか。

    必要なものは Dispose すべきです。

    上記のような例の場合、個人的にはscnだけ解放すればよいような気がするのですが。

    前述のように必要なものは Dispose すべきです。
    提示されている例がどうかは確認していませんが、親のオブジェクトを Dispose すれば子オブジェクトはすべて Dispose されるのであれば、親のオブジェクトだけで良い場合もあります。

    また、int型など、オブジェクトじゃないものはどうしたらいいでしょうか。

    int 型は Dispose メソッドを実装していませんので不要でしょう。
    Dispose を実装するべきは、すぐさまに解放などが必要なものを持つクラスです。(ファイルハンドルとか、アンマネージメモリとか、またはそれらを持つクラスのオブジェクトとか)

    2点目ですが、GC.SuppressFinalize(this)←この文はファイナライズを実行しないということらしいですが、
    ということは、呼び出し元でこのクラスのインスタンスのDisposeを呼んでも、このクラスのインスタンスはプログラムが終了するまで破棄されないということでしょうか。

    Dispose とマネージメモリの回収(ガベージコレクション = GC)は別物です。

    ファイナライザを実装するクラスのオブジェクトは、GC で回収対象になった後も、即座に回収されません。
    ファイナライザの処理が終わった後のどこかのタイミングで回収されることになるため、回収が遅くなります。
    Dispose であらかじめアンマネージメモリなどの解放処理をしており、ファイナライザを呼び出す必要がないことをシステムに通知する(GC.SuppressFinalize)ことで、その回収が遅くなることを防ぐことができます。

    GC などに関しては以下のページを読んでみてください。
    http://msdn.microsoft.com/ja-jp/library/bb985010.aspx
    http://msdn.microsoft.com/ja-jp/library/dd297765.aspx


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年7月13日 14:00
    モデレータ
  • 2010年7月13日 22:34
  • 佐祐理さん

    回答ありがとうございます。

    実装の仕方が書いてあるページがあったのですね。

    2010年7月19日 3:15
  • >Azuleanさん

    回答ありがとうございます。

    ファイナライザとGCがごっちゃになってました。
    マネージメモリについては、ファイナライザを呼び出さなくても回収されるのですね。

    >Dispose を実装するべきは、すぐさまに解放などが必要なものを持つクラスです。(ファイルハンドルとか、アンマネージメモリとか、またはそれらを持つクラスのオブジェクトとか)

    ということは、マネージメモリだけ使うクラスについては、特にDisposeする必要がないということですね。

    >前述のように必要なものは Dispose すべきです。
    >提示されている例がどうかは確認していませんが、親のオブジェクトを Dispose すれば子オブジェクトはすべて Dispose されるのであれば、親のオブジェクトだけで良い場合もあります。

    今回の例の場合、sqlconnectionクラスはクローズ(Dispose)しなければいけないと理解していますが、
    もし、sqlcommandbuilderやsqldataadapterについてもすぐ解放しなければいけないクラスであれば、Disposeメソッド内で
    Disposeしなければいけないということで、
    ただし、sqlconnectionが親クラスとなっており、これを解放(Dispose)すればsqlcommandbuilderやsqldataadapterも解放されるような実装であれば、
    sqlcommanbuilderやsqldataadapterについてはわざわざDisposeを書かなくてもいいということですね。

    それでさらに、sqlcommandbuilderやsqldataadapterがすぐさま解放が必要なクラスでない場合は、そもそもDisposeメソッド内に
    これらのクラスの解放処理(Dispose)を書く必要すらないということでいいですよね。

    もし、そのクラスで宣言しているメンバ変数がすぐに解放すべきものかどうかわからなければ、
    最悪全部のメンバ変数についてDisposeメソッド内で解放(Dispose)すればよいということでよろしいでしょうか。
    (その変数のクラスが解放が必要か調べて必要なものだけDisposeを書くのが一番よいのでしょうが)

    2010年7月19日 3:20
  • もし、そのクラスで宣言しているメンバ変数がすぐに解放すべきものかどうかわからなければ、
    最悪全部のメンバ変数についてDisposeメソッド内で解放(Dispose)すればよいということでよろしいでしょうか。
    (その変数のクラスが解放が必要か調べて必要なものだけDisposeを書くのが一番よいのでしょうが)

    外池と申します。

    結論から言えば、その通りだと思います。詳細は下記の2)をご覧下さい。

    おそらくハッシュドビーフさんの頭の中では既に整理がついているようにお見受けしますが、念のため、2つの事柄をわけて書いておきますと・・・。

    1)あるクラスのオブジェクトが、Disposeメソッドを使って解放操作をしてやるべきものかどうかは、当たり前のような話ですが、Disposeメソッドがあるかどうかで判断できます。DisposeメソッドがあるオブジェクトはDisposeしてやりましょう。逆に、そのようなオブジェクトをメンバ変数で内包しているクラスをご自身で書くときには、メンバ変数をどこかでDisposeするように書いてやらなければならず、場合によってはDisposeメソッドを実装することになります。

    これは、比較的簡単に原則論を書けます。ややこしいのは・・・、

    2) 他の人が書いたクラスのオブジェクト同士が相互に関連しあっていて、あるオブジェクトをDisposeすれば他のオブジェクトも連動してDisposeするようになっている場合、判りにくいことは多々あると思います。本当は、そのクラスのドキュメントをしっかり読めば、判るようになっていないとダメなのですが、そうも言ってられません。とにかく、Disposeできるオブジェクトは、最後にまとめて全部Disposeしてやるように書いておくのが精神衛生上良いかと思います。

    .Net Frameworkの場合は、Disposeし忘れたままあるオブジェクトへの参照がすべてスコープ外になってしまっても、そのうちGC時にDisposeしてもらえる・・・、という安全な構造になっているから、適当でいいんだよ、なんて乱暴な見解もあり得ますが、パフォーマンスを良くする観点から、DisposeできるときにDisposeしておくことが原則と思います。


    (ホームページを再開しました)
    2010年7月19日 8:01
  • >外池さん

    回答ありがとうございます。

    よく分からないクラスについてはとりあえず全部Disposeすることにします。
    心配性の私にはそれが一番いいと思いました。

    (今までに使い慣れていてDispose不要と判断できるものについてはしませんが)

    ということで解決としたいと思います。

    ありがとうございました。

    2010年7月21日 12:48