トップ回答者
GC.SuppressFinalizeは書かないといけないのでしょうか。

質問
-
いつもお世話になっております。
IDisposableの実装時は、下記リンクのようなデザインパターンに従ってコーディングすると思います。
http://msdn.microsoft.com/ja-jp/library/fs2xkftw(v=VS.80).aspx私はファイナライザを記述する必要がない(アンマネージリソースを使っていない)クラスに
IDisposableを実装しようとしているのですが、その場合にはGC.SuppressFinalize()を書く必要がないように
思います。しかし自信が持てないため、
①ファイナライザがないクラスにGC.SuppressFinalize()を書いても不具合は起こらないか。(IDisposableを実装していない場合でも書いてもよいか。)
②IDisposableとGC.SuppressFinalize()はセットで使わないと不具合が起こるのか。(GC.SuppresFinalize()は書かなくてよいか。)
③ファイナライザを記述していないIDisposableを実装したクラスに対して、回答者様は、GC.SuppresFinalizeを記述する/しないのどちらにしているか。についてご教授いただければと思います。
http://msdn.microsoft.com/ja-jp/library/ms182269.aspx
上記リンク先では、ファイナライザを書いていませんが、
これは派生先でファイナライザを書かれる前提で記述しているのでしょう。
しかし、派生先で必ずファイナライザを書くとは限らないことを考えると、
GC.SuppressFinalize()を書いておく分には不具合は起こらない気はしますが…。
(派生クラスでもマネージリソースしか使わなかった場合、ファイナライザは書かないですよね?)ちなみに今回は派生クラスは持たない前提でコーディングしているため、
GC.SuppressFinalize()はいらないのではないかと思いました。ご回答のほど、よろしくお願いいたします。
回答
-
>①ファイナライザがないクラスにGC.SuppressFinalize()を書いても不具合は起こらないか。(IDisposableを実装していない場合でも書いてもよいか。)
ちっちゃなサンプルを動かした分には大丈夫そうな気がします(保証するものではないですが)。
②IDisposableとGC.SuppressFinalize()はセットで使わないと不具合が起こるのか。(GC.SuppresFinalize()は書かなくてよいか。)
IDisposableパターンに則ってファイナライザを実装している場合、GC.SuppresFinalize()を書かなければ、Disposeを呼び出しても、そのオブジェクトはファイナライザの呼び出し待ちになるためいつ呼ばれるかわからないファイナライザが呼ばれるまで、ガベージに回収されなくなります。
すぐに不具合に直結はしないかもしれないですが、蓄積してメモリを圧迫すればそれなりの不具合を起こすかと。
③ファイナライザを記述していないIDisposableを実装したクラスに対して、回答者様は、GC.SuppresFinalizeを記述する/しないのどちらにしているか。
1.sealedして確実に継承されないようにしている
2.アンマネージドリソースを使用していない
3.ファイナライザを実装しない
という条件であればGC.SuppresFinalizeはいらないだろうなと考えています。
- 回答としてマーク ハッシュドビーフ 2010年8月16日 10:56
-
>私の知らない機能があるのであればなにかまずいことが起こるのかもしれません。
心配なら、省略することなんか考えないで、Disposable パターンに忠実に実装すればいいんじゃないでしょうか。
>とのことですので、ファイナライザを書かなくてもGC.SuppressFinalize()は書いても問題ないというように 読み取れるのですが、いかがでしょうか?
単純なケースでは、アンマネージリソースを直接的にカプセルしていないなら
・ファイナライザの記述の必要がない
・IDisposable 内で GC.SuppressFinalize() を呼び出す必要がないということは十分にあり得ます。
ですが、「IDisposable を実装している理由」によっては必要な場合もあるのです。
- 回答としてマーク ハッシュドビーフ 2010年8月16日 10:56
-
横から失礼します。
私もこの辺で悩んだことがありますが、今も明確な答えは持っていません。
ただ、継承元に関してはIDisposableかどうかだけを意識しています。
また、アンマネージリソースは自作クラスでは直接使用しないようにしています。
その上で、(もちろんIDisposableをかくことはありますが)SuppressFinalizeを呼ぶ必要はないと判断しています。
つまり、3に関しては”記述しない”だと思っています。(個人的にです)一方で、記述しても問題ないと考えています。渋木さんの回答で、なぜ記述していないのか?と疑問を起こさせないため書くとありましたが、一理あると思います。
>SqlConnectionやSqlDataAdapterを使うクラスを使いたいため、それをただDispose()で破棄できるようにしたい(というかusingを使えるようにしたい)だけです。
ところで、こういった場合、私はこういったクラスはローカルでしか使用しないようにしています。
業務にもよるのでしょうか?
わたしはDBに詳しくはありませんが、接続は短く!が基本だと思っているので。
DBによらず、ファイルなんかもそうしています。
ま、(アンマネージリソースにまつわる)面倒なことを回避したいからというのもありますが。- 回答としてマーク ハッシュドビーフ 2010年8月16日 10:56
-
他の方も書かれていますが、特定のクラスだけに限定して言うならデストラクタと SuppressFinalize() を共に記載しないのはアリでしょう。
派生クラスが作成される場合、派生クラスがデストラクタを定義して破棄動作を更新することを考えると、SuppressFinalize() は IDisposable のパターンを踏襲しておくほうが、派生クラスを作る側がありがたいと思うはずです。これは、派生クラスからみて「デストラクタが不要になった時、自身の判断で SuppressFinalize() を呼び出す」という判断基準があいまいになることがあるからです。親のことをよく知る直接の子である状況などでは大丈夫かもしれませんが、孫や曾孫にあたるクラスでデストラクションが重要になった場合に、Dispose パターンが踏襲されていて override するメソッドや破棄処理のルートが明確な場合と、破棄処理のうち何が有効で何が無効になっているかわからない状態では、「親は、先祖は、」と実装やドキュメントをどこまで調べなければならないか、といった問題にあたりかねない…ので、一般のクラスであればパターン通りに構築しておくことがおすすめですね。- 回答としてマーク ハッシュドビーフ 2010年8月16日 10:56
すべての返信
-
>(1)ファイナライザがないクラスにGC.SuppressFinalize()を書いても不具合は起こらないか。(IDisposableを実装していない場合でも書いてもよいか。)
>(2)IDisposableとGC.SuppressFinalize()はセットで使わないと不具合が起こるのか。(GC.SuppresFinalize()は書かなくてよいか。)文脈次第です。
GC.SUppressFinalize() は、ヘルプなどで説明されている通りの機能しかありません。
IDisposable.Dispose() 内で GC.SuppressFinalize() を呼び出すのは、その文脈ではそれが妥当だからです。
逆に、IDisposable.Dispose() 内で、GC.SuppressFinalize() 呼出を行わなかった場合にどうなるか、について考察してみましたか?
>(3)ファイナライザを記述していないIDisposableを実装したクラスに対して、回答者様は、GC.SuppresFinalizeを記述する/しないのどちらにしているか。
状況次第です。
サンプル的なものでは省略します。
業務で記述するコードなら、間違いなく IDisposable.Dispose() 内に GC.SuppressFinalize() を記述します。
主に、あとからコードを見た人に「どうして GC.SuppressFinalize() を記述しなかったのか?」という、つまらない疑問をおこさせないためです。
(そういう質問に答える手間を回避したい、というのが本音です) -
>①ファイナライザがないクラスにGC.SuppressFinalize()を書いても不具合は起こらないか。(IDisposableを実装していない場合でも書いてもよいか。)
ちっちゃなサンプルを動かした分には大丈夫そうな気がします(保証するものではないですが)。
②IDisposableとGC.SuppressFinalize()はセットで使わないと不具合が起こるのか。(GC.SuppresFinalize()は書かなくてよいか。)
IDisposableパターンに則ってファイナライザを実装している場合、GC.SuppresFinalize()を書かなければ、Disposeを呼び出しても、そのオブジェクトはファイナライザの呼び出し待ちになるためいつ呼ばれるかわからないファイナライザが呼ばれるまで、ガベージに回収されなくなります。
すぐに不具合に直結はしないかもしれないですが、蓄積してメモリを圧迫すればそれなりの不具合を起こすかと。
③ファイナライザを記述していないIDisposableを実装したクラスに対して、回答者様は、GC.SuppresFinalizeを記述する/しないのどちらにしているか。
1.sealedして確実に継承されないようにしている
2.アンマネージドリソースを使用していない
3.ファイナライザを実装しない
という条件であればGC.SuppresFinalizeはいらないだろうなと考えています。
- 回答としてマーク ハッシュドビーフ 2010年8月16日 10:56
-
渋木宏明さん
ご回答ありがとうございます。
>GC.SUppressFinalize() は、ヘルプなどで説明されている通りの機能しかありません
ファイナライザを呼び出さないようにビットフラグを立てるという機能だった気がしますが、 そのビットが立つことによる影響がどんなものなのかわかりませんでした。
ビットフラグが立つことにより何か悪さをしないのであれば(ファイナライザを呼び出さないようにするだけであれば)、 ファイナライザがないクラスに対してGC.SuppressFinalize()を記述しても不具合はないと考えます。いかがでしょうか?>逆に、IDisposable.Dispose() 内で、GC.SuppressFinalize() 呼出を行わなかった場合にどうなるか、について考察してみましたか
私の考察では、ファイナライザを呼び出さないようにするだけだと思っていますので、 ファイナライザのないクラスに対しては、GC.SuppressFinalize()の呼び出しを行わなくてもなんの影響もないと考えますが…。 私の知らない機能があるのであればなにかまずいことが起こるのかもしれません。
>業務で記述するコードなら、間違いなく IDisposable.Dispose() 内に GC.SuppressFinalize() を記述します
とのことですので、ファイナライザを書かなくてもGC.SuppressFinalize()は書いても問題ないというように 読み取れるのですが、いかがでしょうか?
文脈次第とのことですが、ファイナライザは書かない場合としていますので、ファイナライザが必要かどうかの判断はできている前提でお願いします。
というのも、GC.SuppressFinalize()はファイナライザを呼ばない機能だけだと私は思っているからです。
よって、ファイナライザを書く必要がある文脈か書かなくてよい文脈かだけになると思うのですが、そうでもないのでしょうか。とりあえずコードを乗せようと思うのですが、今自宅におりコードがないため、月曜(もしくはその数日後)になるのですが、お時間がおありでしたら判断願えませんでしょうか?
口頭で簡単に申し上げると、SqlConnectionやSqlDataAdapterを使うクラスを使いたいため、それをただDispose()で破棄できるようにしたい(というかusingを使えるようにしたい)だけです。長くなってしまい恐縮です。
よろしくお願いします!!- 編集済み ハッシュドビーフ 2010年8月14日 6:47 わかりにくいところを修正しました。
-
Ayasamさん
ご回答ありがとうございます。
> ちっちゃなサンプルを動かした分には大丈夫そうな気がします(保証するものではないですが)。
とのことですが、これが保証されない機能だとすると、私が貼った2番目のリンク
http://msdn.microsoft.com/ja-jp/library/ms182269.aspx
に書いてあるコーディングはまずいコーディングになってしまうと思うのですが…。
> 1.sealedして確実に継承されないようにしている
2.アンマネージドリソースを使用していない
3.ファイナライザを実装しない
という条件であればGC.SuppresFinalizeはいらないだろうなと考えています。この条件で使用する予定です。Sealedはしない予定なのですが、大規模開発の部品を作る作業というわけではないため、自分以外の人は使いません。
必要であればSealed致します。結局ファイナライザも含めて全部書かないといけないのでしょうか。
ファイナライザはできるだけ書くなとmsdnライブラリのどっかに書いてあった気もしますし…。うーん。とりあえず渋木宏明さんのところにも書きましたが、コードを載せますので判断してもらえないでしょうか。
よろしくお願いします。 -
ファイナライザを呼び出さないようにビットフラグを立てるという機能だった気がしますが、 そのビットが立つことによる影響がどんなものなのかわかりませんでした。
ビットフラグが立つことにより何か悪さをしないのであれば(ファイナライザを呼び出さないようにするだけであれば)、 ファイナライザがないクラスに対して記述しても不具合はないと考えます。
補足ですが、すべてのクラスにobject.finalize()は継承されていますが、自作クラスにファイナライザを記述しなければ、ファイナライザは呼び出されない(記述していないのと同等)だと思ってコメントを書いております。
そのあたりから違ってたらご指摘ください。 -
>私の知らない機能があるのであればなにかまずいことが起こるのかもしれません。
心配なら、省略することなんか考えないで、Disposable パターンに忠実に実装すればいいんじゃないでしょうか。
>とのことですので、ファイナライザを書かなくてもGC.SuppressFinalize()は書いても問題ないというように 読み取れるのですが、いかがでしょうか?
単純なケースでは、アンマネージリソースを直接的にカプセルしていないなら
・ファイナライザの記述の必要がない
・IDisposable 内で GC.SuppressFinalize() を呼び出す必要がないということは十分にあり得ます。
ですが、「IDisposable を実装している理由」によっては必要な場合もあるのです。
- 回答としてマーク ハッシュドビーフ 2010年8月16日 10:56
-
横から失礼します。
私もこの辺で悩んだことがありますが、今も明確な答えは持っていません。
ただ、継承元に関してはIDisposableかどうかだけを意識しています。
また、アンマネージリソースは自作クラスでは直接使用しないようにしています。
その上で、(もちろんIDisposableをかくことはありますが)SuppressFinalizeを呼ぶ必要はないと判断しています。
つまり、3に関しては”記述しない”だと思っています。(個人的にです)一方で、記述しても問題ないと考えています。渋木さんの回答で、なぜ記述していないのか?と疑問を起こさせないため書くとありましたが、一理あると思います。
>SqlConnectionやSqlDataAdapterを使うクラスを使いたいため、それをただDispose()で破棄できるようにしたい(というかusingを使えるようにしたい)だけです。
ところで、こういった場合、私はこういったクラスはローカルでしか使用しないようにしています。
業務にもよるのでしょうか?
わたしはDBに詳しくはありませんが、接続は短く!が基本だと思っているので。
DBによらず、ファイルなんかもそうしています。
ま、(アンマネージリソースにまつわる)面倒なことを回避したいからというのもありますが。- 回答としてマーク ハッシュドビーフ 2010年8月16日 10:56
-
他の方も書かれていますが、特定のクラスだけに限定して言うならデストラクタと SuppressFinalize() を共に記載しないのはアリでしょう。
派生クラスが作成される場合、派生クラスがデストラクタを定義して破棄動作を更新することを考えると、SuppressFinalize() は IDisposable のパターンを踏襲しておくほうが、派生クラスを作る側がありがたいと思うはずです。これは、派生クラスからみて「デストラクタが不要になった時、自身の判断で SuppressFinalize() を呼び出す」という判断基準があいまいになることがあるからです。親のことをよく知る直接の子である状況などでは大丈夫かもしれませんが、孫や曾孫にあたるクラスでデストラクションが重要になった場合に、Dispose パターンが踏襲されていて override するメソッドや破棄処理のルートが明確な場合と、破棄処理のうち何が有効で何が無効になっているかわからない状態では、「親は、先祖は、」と実装やドキュメントをどこまで調べなければならないか、といった問題にあたりかねない…ので、一般のクラスであればパターン通りに構築しておくことがおすすめですね。- 回答としてマーク ハッシュドビーフ 2010年8月16日 10:56
-
まとめて返信させて下さい。
http://gushwell.ldblog.jp/archives/2009-11.html
上記リンク先では、sealedクラスによって、私が言ってきたような形で実装していますので、
今回のような質問に限って言えば、ファイナライザとsuppressfinalizeは両方とも書かないようなコーディングにしようと思います。一応月曜日にコードを載せると書きましたので、一部載せますが、一応解決としたいと思います。
ありがとうございました。
渋木宏明さん
回答ありがとうございます。
>心配なら、省略することなんか考えないで、Disposable パターンに忠実に実装すればいいんじゃないでしょうか。おっしゃるとおりです。最悪の場合そうしようと思ったのですが、やはり一般的なコーディングを身につけることが大事だと思いましたで、しぶとく質問させていただきました(笑)
IDisposableを実装する理由によっては、必要な場合があるのですね。了解です。
私自身のレベルがあがったらその辺の質問が出ることがあるかもしれません。
これからもなにとぞよろしくお願いします。-toyo-さん
回答ありがとうございます。
わたしもちょこっとだけDB接続について調べてみましたが、おっしゃるとおり、長くつないでおくよりは細かい接続を何度もやったほうがいいみたいですね。(たぶん)
その辺はちょっと見直そうかなと思います。K.Takaokaさん
いつも回答ありがとうございます。
sealedしない場合には、IDisposableパターンにのっとり、ファイナライザもsuppressfinalizeも両方とも記述するようにしようと思います。
Assertという新たな手法が登場しましたが、後日調べたいと思います。
とりあえず今回の問題は解決としたいと思います。
最後に自分のコードを載せます。using System; using System.Collections.Generic; using System.Text; using System.Data; using System.Data.SqlClient; namespace AAA { public class AAASample:IDisposable { private bool _disposed; public DataTable _DTable; private string _ConStr; private string _SelStr; private string _InsStr; private SqlConnection _SqlConn; private SqlDataAdapter _SqlDa; private SqlCommandBuilder _CmdBldr; public AAASample { //初期化 } /**********************/ /*****いろいろなメソッド****/ /**********************/ #region IDisposable メンバ //コメントアウト /*~AAASample() { Dispose(false); }*/ //GC.SuppressFinalize()はコメントアウト public void Dispose() { Dispose(true); // Take yourself off the Finalization queue // to prevent finalization code for this object // from executing a second time. //GC.SuppressFinalize(this); } //引数無しDispose()に書けばよいのだが、以下も作成 private void Dispose(bool disposing) { if (!this._disposed) { if (disposing) { if (_DTable != null) { _DTable.Dispose(); _DTable = null; } if (_CmdBldr != null) { _CmdBldr.Dispose(); _CmdBldr = null; } if (_SqlDa != null) { _SqlDa.Dispose(); _SqlDa = null; } if (_SqlConn != null) { _SqlConn.Dispose(); _SqlConn = null; } } } _disposed = true; } #endregion } }
いろいろ突っ込んでいただいてもいいですし、スルーしていただいても構いません。
とりあえず期限が近いためこれで行きたいと思います。
-
今頃ですが、先頭から読んで思ったことを返信させていただきます。
派生可能な Dispose のパターンでは、派生先でアンマネージリソースが利用される可能性への準備として、たとえそれ自身ではアンマネージリソースを保持していなくてもファイナライザの実装(引数付 Dispose の呼び出し)を行い、IDisposable.Dispose では GC.SuppressFinalize(this) を実行すべきと考えます。
そうしておけば、派生クラスでは Dispose(bool disposing) をオーバーライドすればよいだけとなり、GC.SuppressFinalize の呼び出しやファイナライザの実装は派生元に任せることができて単純になります。
そのため、次のように考えます。
・①は問題があるはずはないと考えます。
・②は通常はセットになり、そうならないのは変だと思います。
・③は上述の通りです。
ただし、アンマネージリソースを保持しないクラスがファイナライザを実装することを問題視する人にとっては、少し違ったパターンを実装するのかもしれません(いないかもしれませんけど)。
それと、IDisposable を実装する sealed なクラスでは、③については意味がなければ記述する必要はないと思います。
Ayasam さん
> GC.SuppresFinalize()を書かなければ、Disposeを呼び出しても、そのオブジェクトはファイナライザの呼び出し待ちになるためいつ呼ばれるかわからないファイナライザが呼ばれるまで、ガベージに回収されなくなります。
Ayasam さんの②に関するこの説明ですが、少し間違ってますよね?
解放すべきリソースは Dispose の呼び出しによって解放済みですし、GC.SuppresFinalize の呼び出し有無に関係なくガベージは行われますので。(GC.SuppresFinalize はファイナライザの抑制だけです。)
ハッシュドビーフさん
> とのことですが、これが保証されない機能だとすると、私が貼った2番目のリンク
> http://msdn.microsoft.com/ja-jp/library/ms182269.aspx
> に書いてあるコーディングはまずいコーディングになってしまうと思うのですが…。
保証されるため問題ないと考えますが、冒頭の通り、私としてはファイナライザの定義が省略されてしまっている点は良くないと思いました。
単なる警告有無のサンプルという位置づけでしょうから、どちらでもいいですけど。
ハッシュドビーフさん
> いろいろ突っ込んでいただいてもいいですし、スルーしていただいても構いません。
ただの記述漏れかと思いますが、「sealedしない場合には、IDisposable パターンにのっとり」と書かれていますが、そうはなっていませんね。(sealed を記述しなくても、ソースの保守管理上、派生することはあり得ないという事情があることも十分考えられますけど。)
それとフォーラムで最近話に出たスレッドセーフ対応も、virtual void Dispose(bool disposing) 内で実装してしまうと、パターンの一部にできると思います。今回そこまでは不要でしょうけど。
追記:
①は「そのため」じゃないですね。文章を変更していたので変になりました。
Object がファイナライザを持っていることを考えれば、「ファイナライザがないクラス」はないのではと思いました。
それと②はあくまで私見です。
追記2:
些細なことですが、誤解を与えそうな点を訂正させていただきます。
「保証されるため問題ないと考えますが」と書きましたが、「問題ないことが保証されていると考えますが」の意味になります。
保証されているという情報ソースがあるわけではありません。- 編集済み TH01 2010年8月17日 6:20 追記2
-
-
To:TH01さん
>私もファイナライザが実装されている上での話として書きました。
>それと、「Disposeを呼び出しても」と書かれていますので、解放対象のリソースは解放済みなことを前提としています。
>そのうえで GC.SuppresFinalize の有無が(何かの)ガベージ回収のタイミングに影響すると読めましたので、それは違うと思ったのでした。
私自身は以下のようなつもりでいましたが、どうでしょう?
ファイナライザが実装されている状態で、Dispose時にGC.SuppresFinalizeを呼び出さなければ、
そのオブジェクトはファイナライズの抑制がされていないため、ファイナライズの対象のままということになると思います。
ファイナライズ対象のままとなっていれば、そのオブジェクト内に保持するリソースを既に開放していたとしても、ガベージコレクションではファイナライズ実行待ちのキューに積まれるだけで回収されないと思います。
ファイナライズ実行待ちのキューに積まれたオブジェクトは、ファイナライズ用のスレッドでファイナライズが呼び出されて、ようやくガベージの対象となるので、最低でもガベージコレクション1回分は回収が遅れるかと思います。 -
Ayasam さんが書かれた内容が次のサイトに詳しく書かれていました。
囚人のジレンマな日々
This is CLR - GC (Section 1)
http://blogs.wankuma.com/shuujin/archive/2007/03/17/67216.aspxAyasam さんが
> 回答の仕方が不十分でしたね。
と書かれた意味がようやくわかりました。今そう思いました。
嘘です。失礼しました。。