デストラクタについて

タグの編集
投稿者 みどり  () 投稿日時 2008/7/14 20:23:00
全て表示
投稿者 みどり  () 投稿日時 2008/7/15 18:13:00
ご回答ありがとうございます。 
お礼が遅くなり申し訳ありません。 
結局のところ勉強不足のためよく理解できないというのが感想です。 
皆様の意見を参考に自分なりに導きだした答えは、 
とりあえず、特に何も処理を行う必要がない場合でも将来の改造に備えて Dispose メソッドを実装しておきは空にしておき、使う側は独自に作成したクラスには必ず Dispose メソッドがあるのでとりあえず呼んでもらう。 
また、派生クラスで親クラスに Dispose メソッドが存在する場合は子クラスの Dispose メソッドで MyBase.Dispose を実行する。 
あと、使う側で Dispose メソッドが実行されなかった場合の保険として Finalize メソッドを実装しておきガベージコレクタ任せになるけれども 親クラスの Finalize メソッドを呼び出すようにしておく(派生クラスの場合)。 
といった感じなんですが、どうでしょうか? 
 
クラスを使う側からみた場合にコンストラクタは非共有メンバにアクセスするたのインスタンスに必ず使用している形になっていますが、Dispose は参考プログラム等ではあまり実行されている様子はありません。 
恐らくは参考プログラムなので割愛されているのだとは思いますが、使用したクラスに Dispose が実行されている場合は必ず実行しておくように心掛けておいたほうがよいのでしょうか?(初級講座第2回の Pen クラス等)
投稿者 よねKEN  () 投稿日時 2008/7/15 18:52:00
>>5 
私の2の投稿で提示したURLはご覧になりましたでしょうか? 
 
> とりあえず、特に何も処理を行う必要がない場合でも将来の改造に備えて Dispose メソッドを実装しておきは空にしておき、使う側は独自に作成したクラスには必ず Dispose メソッドがあるのでとりあえず呼んでもらう。 
 
上記のURLを読んだ上での判断であれば止めはしませんが、すべてのクラス/構造体にDisposeメソッドを実装するというのは乱暴な処置ですのでお勧めしません。 
 
・クラス/構造体を新たに定義する度に、意味もなくIDisposableインターフェースの実装、及び、Finalizeメソッドのオーバーライドが必要。 
  →クラス/構造体を定義する人は皆が一様にIDisposable、Finalizeの正しい実装方法を知っている必要がある。 
 
・クラス/構造体の利用側も、意味も解らずにとりあえずDisposeを呼ばなければならない 
  →呼び忘れが発生する。 
 
・必ず呼ばなければならない、本当に意味のあるDisposeメソッドが大量のDisposeメソッドの呼び出しに埋もれてしまう。また、本来の処理上で重要なメソッド以上にDisposeメソッドの呼び出しばかりになり、処理の流れが追いづらい。 
 
・自作クラス/構造体は必ずDisposeメソッドを呼ぶことになるが、.NET FrameworkのクラスはDisposeの必要なものもそうでないものもあるため、一部にDisposeメソッドを呼べない部分が残り、結局はDisposeメソッドの呼び出しが必要かどうかを取捨選択する必要がある。 
 
といった理由です。
投稿者 よねKEN  () 投稿日時 2008/7/15 19:40:00
>>5 
 
長いのでコメントを2つに分けました。 
 
> また、派生クラスで親クラスに Dispose メソッドが存在する場合は子クラスの Dispose メソッドで MyBase.Dispose を実行する。  
 
派生クラスのDisposeメソッドで+αの処理を行うのでなければ、親クラスのDisposeメソッドをオーバーライドする必要はありません。無用なオーバーライドは避けましょう。 
 
> 使用したクラスに Dispose が実行されている場合は必ず実行しておくように心掛けておいたほうがよいのでしょうか? 
 
IDisposable.Disposeメソッドが実装されているものに対しては、可能な限りDisposeメソッドを呼ぶべきです。 
ただし、常に呼び出せるとは限りません。 
 
・自分自身でそのクラス/構造体のインスタンスを作成していること 
・他のクラス等でそのインスタンスを使用していないことが明らかなこと 
 
といったいくつかの条件が揃っている必要があります。 
他で使われているインスタンスのDisposeメソッドを呼び出すとまずいですので。 
例えば、メソッドのローカル変数にインスタンスを生成し、そのメソッドの中でだけそのインスタンスを利用しているなら、通常は、Disposeメソッドを呼んで問題ありません。 
 
ちなみにVB2005以降なら、Using句というDisposeメソッドを必ず呼び出すための構文がありますので、これを利用するのをお勧めします。
投稿者 魔界の仮面弁士  () 投稿日時 2008/7/15 20:04:00
>>5 
> 将来の改造に備えて Dispose メソッドを実装しておきは空にしておき、 
それは避けましょう。将来、使う「かもしれない」という理由だけで 
空のメソッドを追加していったら、まとまりの無いクラスになってしまいます。 
 
そのクラスが、「IDisosable を実装しないと、内部で利用している 
オブジェクトを正しく破棄できない」事が分かっている場合のみ、 
実装するようにしてください。 
 
 
> 必ず Dispose メソッドがあるのでとりあえず呼んでもらう。 
できれば、理由を把握した上で意図的に呼ぶようにしてください。 
初めの内は「とりあえず呼ぶ」という覚え方でも良いですが、 
それだけでは対処できないパターンも多々存在します。 
 
たとえば、PictureBox1.CreateGraphics() で生成した Grapchis は、 
責任を持って Dispose せねばなりませんが、Paint イベントの引数 
e.Graphics で得た Grapchis は、Dispose してはなりません。 
 
また、 
 Dim p1 As New Pen(Color.Red) 
 Dim p2 As Pen = Pens.Red 
とあった場合、p1 は「Dispose しなければならない」のですが、 
逆に p2 は「Dispose してはいけない」ものだったりします。 
 
これは、Using ステートメントを使って破棄する場合も同様ですし 
標準のクラスではなく、自作クラスであっても同じ状況になりえます。 
 
 
> また、派生クラスで親クラスに Dispose メソッドが存在する場合は 
> 子クラスの Dispose メソッドで MyBase.Dispose を実行する。 
ここでいう「派生クラス」と「子クラス」は同じ物ですか? 
それとも別物…たとえば「親=Form、派生=Form1、子=Controls」とか? 
 
いずれにせよ、多くの場合、子クラスで Dispose メソッドを 
再定義(オーバーライドもしくはシャドウイング)する必要は無いはずです。
投稿者 魔界の仮面弁士  () 投稿日時 2008/7/15 20:08:00
>>5 
http://msdn.microsoft.com/ja-jp/library/cc465420.aspx 
上記の記事において、下記のような一文があります。 
 
『そのため、「安全のためだけ」という理由で Finalize をオーバーライドすることは避け、必ず実際にファイナライズが必要な場合にだけオーバーライドしてください。』 
 
 
> あと、使う側で Dispose メソッドが実行されなかった場合の保険として 
> Finalize メソッドを実装しておきガベージコレクタ任せになるけれども 
> 親クラスの Finalize メソッドを呼び出すようにしておく(派生クラスの場合)。 
親クラスの前に、自分自身が持っていたオブジェクトの破棄を考えておきましょう。 
 
破棄対象には、マネージオブジェクト(.NET で管理されるオブジェクト)と 
アンマネージオブジェクト(API で得たハンドルや、COM オブジェクト等)の 
2 種類があります。この違いが重要になってきます。 
 
IDisposable.Dispose では、この両者を解放するように実装します。 
そして Finalize の方は、Dispose 済みならば何もせず、Dispose されて 
いなければ、アンマネージオブジェクトのみを解放させるように実装します。 
(ファイナライズの時点で、マネージオブジェクトを操作してはいけません) 
 
ただし通常、マネージオブジェクトの破棄処理は実装すべきではありません。 
大量のメモリを確保していたような状況など、破棄処理を考慮した方が 
良い場合もあるのですが、ほとんどの場合においては、自分で処理するよりも 
ガベージコレクタに任せた方が良い結果となります。 
 
結果として、IDisposable を実装するのは、アンマネージオブジェクトを 
内包している場合にほぼ限られてきます。 
 
 
> 非共有メンバにアクセスするたのインスタンスに必ず使用している 
厳密には、非共有メンバへのアクセスを発生させることのない、 
Shared Sub New() というコンストラクタも存在します。
投稿者 るしぇ  () 投稿日時 2008/7/15 20:56:00
一本化したルールで覚えて問題無いなら、手法を統一して覚える手間を省き 
ミスを少なくしたいという思いはあるのですが、Dispose は難しいですかねぇ。 
 
るきおさん提示のリンク先から更に 
[オブジェクトの有効期間 : オブジェクトの作成と破棄] 
http://msdn.microsoft.com/ja-jp/library/hks5e2k6(VS.80).aspx 
があるわけですが、の時代にクラスの初期処理と終了処理を 
Class_Initialize と Class_Terminate に書いていました。 
# フォームだと Load イベントに書く場合も多かったですが。  
 
インスタンスの生成時は New だけど、終了処理は?って時に迷います。 
ここに書いておけば終了時に実行され、Base クラスで共通化されてる 
ような終了処理が欲しかったです。 
 
ただ、お話を聞いてると Dispose はリソースの破棄が無い場合は 
実装しない方が良さそうですかねぇ。 
でも、それだと終了処理を共通化できるようなコードは難しいですか。 
# 対象のオブジェクトに Dispose が実装されているかどうかを判断するの 
# って、キャストでエラートラップになりますよね? 
 
>使用したクラスに Dispose が実行されている場合は必ず実行しておくように心掛けて 
>おいたほうがよいのでしょうか?(初級講座第2回の Pen クラス等) 
Button1_Click で CreateGraphics してる場合などは実行すべき場面です。 
しかし、Paint イベントの e.Graphics では実行してはいけません。 
理由は 
>・自分自身でそのクラス/構造体のインスタンスを作成していること 
による違いです。 
 
やっぱり個々の場面毎に覚えていく必要もあるなぁ。。。
投稿者 魔界の仮面弁士  () 投稿日時 2008/7/15 22:51:00
>>11 
> # 対象のオブジェクトに Dispose が実装されているかどうかを判断するの  
> # って、キャストでエラートラップになりますよね?  
普通は、Using を使います。コンパイル時に解決されますので都合が良いですよ。 
 
実行時に解決するなら、TypeOf や TryCast で判定します。 
エラー判定は出来る限り使うべきでは無いかと。
投稿者 みどり  () 投稿日時 2008/7/16 00:58:00
みなさんありがとうございます。 
 
よねKENさん、すみません教えて頂いたURLを開いてみましたが、結局のところよく分からず混乱してしまい、せっかくのアドバイスの真逆を行ってしまったみたいです。 
 
使ったクラスのヘルプ等に使用後には Dispose を実行するようにといった記述がある場合や自作のクラスでファイルや通信回線のクローズを確実に行う必要がある場合に実装するようにします。 
 
Using 句についても読んで見ましたが、今の知識ではよく理解できません。 
 
そもそも今回の質問の発端はクラスの破棄を自発的に正しく行わないとガベージコレクタ任せになってしまう心配(実際そうなのかは分かりませんが)があったからです。 
 
昔のBASIC(N88等)にもガベージコレクタが文字変数の掃除をしていた記憶があり、最悪プログラムのだんまり状態が結構長く続いた記憶があったもので... 
 
VB.Netのガベージコレクタの処理も場合によれば結構時間がかかるものなのでしょうか?
投稿者 るしぇ  () 投稿日時 2008/7/16 04:50:00
>普通は、Using を使います。 
>実行時に解決するなら、TypeOf や TryCast で判定します。 
すいません。まだボクの周りの主流が.NET2003なので^^; 
  If TypeOf obj Is IDisposable Then 
で判定できました。ありがとうございます。 
 
>VB.Netのガベージコレクタの処理も場合によれば結構時間が 
>かかるものなのでしょうか? 
お掃除に掛かる時間のことでしょうか? 
お掃除を始めるまでの時間のことでしょうか? 
 
メモリが足りなくならないとガベージコレクタが動かないっていう 
話があって、SPREAD を貼り付けたフォームを大量に生成して閉じる 
処理でテストしたことがあります。タスクマネージャでメモリ使用量が 
8割を超えるくらいまでずーっと増え続ける現象を確認しました。 
もちろん Dispose してます。 
 
結構溜め込んでいたのでメモリ使用量が減ってから落ち着くまでの 
処理時間は2.5秒くらいでした。 
 
そんなことを某掲示板で書いていたら『ガベージコレクタの動きは 
環境によって変わります』って書き込みがありました。でも、ボクの 
周りで確認できているのは、この増え続けて足りなくなった時点で 
数秒かけて減るというパターンのみですね。 
 
>クラスの破棄を自発的に正しく行わないとガベージコレクタ任せに 
>なってしまう心配 
自発的に正しく破棄してもガベージコレクタ任せですよ? 
Dispose したから即メモリ解放ではありません。 
[ガベージコレクション入門] 
http://www.microsoft.com/japan/msdn/net/mag00/GCI.aspx 
http://www.microsoft.com/japan/msdn/net/mag00/GCI2.aspx
投稿者 けろ-みお  () 投稿日時 2008/7/16 08:36:00
>>15  
 
コメントしようか悩みましたが、やはり気になったので、お許し下さい。  
 
>8割を超えるくらいまでずーっと増え続ける現象を確認しました。  
>もちろん Dispose してます。  
 
この場合、Disposeだけではなく、Nothingをセットすることが必須になりますが、Nothingしてもこの現象なのでしょうか?  
もしNothingしていないのであれば、単純にメモリを食いつぶしている可能性もありますね。  
 
下記にもわかりやすく掲載されてますので、ご参考になれば幸いです。  
 
http://blogs.wankuma.com/mura/archive/2007/08/01/88160.aspx