トップ回答者
マネージリソースのみで構成されているクラスにIDisposeを実装するメリット

質問
-
質問1
Class1のようにマネージリソースのみで構成されているクラスの場合、わざわざIDisposeを実装するメリットはありますか?質問2
呼出例1は呼出例2に比べてメリットがありますか?
// Class1呼出例1 IDispose実装時
using(Class1 class1 = new Class1())
{
Console.WriteLine(class1.GetAverage().ToString());
class1.Dispose();
// 何か別の処理
....
}// Class1呼出例2 IDispose非実装時
Action act = () =>
{
Class1 class1 = new Class1();
Console.WriteLine(class1.GetAverage().ToString());
class1 = null;
// 何か別の処理
....
};
act();// Class1の定義
public sealed class Class1 : IDisposable
{public Class1()
{
_bigarray = new double[100000];
Random rnd = new Random();
for (int index = 0; index < _bigarray.Length; index++)
{
_bigarray[index] = rnd.NextDouble();
}
}~Class1()
{
Dispose(false);
}public double GetAverage() { return _bigarray.Average(); }
public void Dispose()
{
// スレッドセーフにします。
lock (this)
{
// リソースを破棄します。
Dispose(true);
// デストラクタが呼び出されないようにします。
GC.SuppressFinalize(this);
}
}private void Dispose(bool disposing)
{
if (disposing)
{
// ここでマネージリソースを破棄します。
if (_bigarray != null) _bigarray = null;
}
// ここで非マネージリソースを解放します。
}private double[] _bigarray;
}
C#開発者
回答
-
非常に根本的な話ですが、「IDisposable(Dispose) はリソースを解放することを目的としたメソッドではない。」という認識はありますでしょうか?
単純に「プログラマが任意のタイミングでオブジェクトの状態を(破棄状態へ)変化させるための機能」を統一的に提供するための定義であることが役割であって、その実装の代表例がリソースの解放ですよね。リソースの解放とセットで語られることが多いですが、本来の目的は、オブジェクトの回収/破棄が管理されたマネジドな世界において「GC によるオブジェクト回収」というタイミングや実行コンテキストの制御が難しい部分から、手軽に抜け出すためのものでしかないと思います。リソースの解放を行うことを主目的とせず、オブジェクトの役割として状態変化を意図した IDisposable の実装というのは、クラスライブラリよりもアプリケーションの実装のほうで良く見ますが、そんなに珍しい用法だとは思いません。System.Transaction ネームスペースの TransactionScope なんかは、内部的にはアンマネージリソースも保持しているようなものでしょうけれど、クラスデザインとしてはリソースの破棄をするために IDisposable を実装しているというよりも、スコープの終了をプログラマが明示するために IDisposable を実装していると思います。(そして、その都合で using と相性がよい)
- 回答としてマーク MicroVAX 2010年9月2日 10:06
-
外池です。すいません、確認ですが、質問1と質問2のclass1は同じものですよね?つまり両方とも非マネージリソースは内包していないということで? その前提で、
質問1については、IDisposableを実装するメリットは無いです。したがって、質問2については、呼出例2で十分です。
-------
なお、プログラムの可読性は別にして、動作の観点だけからコメントしますと、
呼出例1ですが、usingでclass1を明示しつつclass1.Disposeを呼ぶのは冗長です。usingのブロックを抜けるときに自動的にDisposeが呼ばれるからです。呼出例2についても、class1=nullの代入も冗長です。
(ホームページを再開しました)- 回答としてマーク MicroVAX 2010年9月2日 10:08
-
それと、null を代入することの意義については、こちらも参考になると思います。
Dispose()とnull代入
http://social.msdn.microsoft.com/Forums/ja-JP/vsgeneralja/thread/65c3c5fe-036a-457b-9d6a-0e418f0f4770
追記:
書かれた呼出例2の場合、null を代入すること自体に、.NET としては意味がありません。
(と、上記スレッドで学びました。) -
.NET のオブジェクトとしてのメリットはありませんが、多くの .NET 対応のプログラム言語が IDisposable に対する便利な構文をサポートしており、それを享受できるというメリットがあります。C# では using ブロックがあり、
public class WaitCursor : IDisposable { public WaitCursor() { this.OldCursor = Screen.Cursor; Screen.Cursor = Cursors.Wait; } private Cursor OldCursor; private IDIsposable.Dispose() { Screen.Cursor = this.OldCursor; } }
こんなマネジドクラスなら、
using (new WaitCurosor()) { /* 時間のかかる処理*/ }
みたいな使い方ができます。
- 回答としてマーク MicroVAX 2010年9月2日 10:07
-
外池です。
MicroVAXさんのお示しになった例は、マネージドリソースだけを着目したときのDisposeのメリットの有無やGCのタイミングと効能を考える上で、良い例だと思います。
私は、TH01さんとまったく同じ理解をしています。
マネージドな配列や、代入したり、繋いだり、切ったりしまくったstringの断片たちのように、一見、ローカル変数で使っているように見えても、実際はヒープに入っているオブジェクトが今の話題の対象ですよね? で、変数にnullを代入したり、あるいは、ローカル変数がスコープから外れて、ヒープにある本体データへの参照が切れてしまったら、あとはGCを待つしかありません。クラスのメンバー変数で参照する場合で、IDisposableを実装してそこでnullを代入したとしても、やはりあとはGC待ちしかない、という点で、事情は同じです。
GCが何回か繰り返される間、ずっとスコープ内にとどまるような寿命の長い変数が参照するオブジェクトで大きなリソースであれば、TH01さんの仰るとおり、無用になればnullを代入しておくことが、メリットがあると思います。当たり前ですが本当に全体リソースが逼迫したときは、無用なものはnullにしておかないとGCしてもらえなくてパンクしちゃいます。
(ホームページを再開しました)- 回答としてマーク MicroVAX 2010年9月2日 10:07
-
外池です。マネージドな配列やマネージドな値型の変数ばかりを使って数値計算をするような状況であれば、IDisposableの実装は要らないです。マネージドな配列が不要になれば、すべての参照を断ち切る(ある不要になった配列を参照しているスコープ内の変数すべてにnullを代入する)ことができれば、自動的にGCされます。(最初の太字下線部については、そのとおりです。)
強制的にGCすることが良いかどうかは、物理メモリーの搭載量、ページファイルの最大サイズ、あと、プログラムでどのように配列を使っているのか(必要になるタイミング、必要な量、不要になるタイミング)によりますので、なんとも言えません。
太字2番目の2つ目のご質問ですが、GCの動作について背景知識が必要になります。あるオブジェクトがどこからも参照されていない、つまり不要であることを検知する動作と、デストラクタを呼び出す動作と、最終的に占有していたメモリーを解放する動作がどのような段階を踏むかという問題です。私も詳しくないのですが・・・、大体、以下のような感じです。
1) あるデストラクタが存在するオブジェクトへの参照が無くなった場合ですが、GCの1回の動作で不要であることが検知されて一旦終わってしまいます。次のGCの動作においてデストラクタが実行されて占有していたメモリーが解放されます。
2) デストラクタが存在しないオブジェクトへの参照が無くなった場合は、GCの1回の動作で不要であることが検知されて、すぐに占有していたメモリーが解放されます。
SupressFinalizeを使うと、デストラクタがあるオブジェクトでも2)の動作を強制できます。
このあたりは、IDisposableのドキュメント、GCのドキュメントをじっくり読むことをお奨めしますし、デザインパターンはキチっと守るのが良いと思います。
繰り返しになりますが、
A) マネージリソース「だけ」を扱うのであれば、IDisposableもデストラクタも考える必要はありません。
B) 一見マネージドなオブジェクトに見えても非マネージリソースを扱っていて、IDisposableが実装されているオブジェクトがあります。このようなオブジェクトを内包するオブジェクトをプログラムされる際には、やはりIDisposableを実装すべきですが、public void Dispose()と、private void Dispoas(bool disposing)のうちdisposing==trueの処理があれば足ります。デストラクタは不要なはずです。
C) 作ろうとしているオブジェクトが非マネージリソースを直接扱う場合、例えば、非マネージリソースを確保してそのハンドルを保持するような場合ですが、IDisposableをすべて実装して、デストラクタも必要になります。このオブジェクトが不要になるごとにDisposeを必ず呼び出してもらえるならデストラクタは要らないのですが、Disposeを忘れてしまった場合でも、GCの際にデストラクタを呼び出してもらえるので安全です。
(ホームページを再開しました)- 回答としてマーク MicroVAX 2010年9月2日 10:06
-
To:MicroVAXさん
見直してみましたが、MSDNにあるDisposeパターンのサンプルなどで書かれているものと、
MicroVAXさんのもっている「リソース」の認識がずれているような気がします。
・float[] hugearray;など
→これらは.Netの管理するヒープメモリ上に生成されるオブジェクト
→ いわばサンプルなどで言われるリソースではない。
・ファイルIO、ネットワーク接続、ポインター等
→ これらをリソースとよんでいるはずです。
その上で、上記のようなリソースを内応するマネージオブジェクトをマネージリソースとよんでいると思います。
→例: System.Drawing.Imaging.Bitmap hugebitmap;など
非マネージリソースは?
→ C++の関数をインポートして取得したリソースのIntPtr、unsafeで取得してきたリソースのIntPtrなど
と思います。
ということで、例に挙がっているClass1は、そもそもリソースを保持していないので、Disposeパターンの実装の必要が無いと思います。
ですので、
>Class1のような場合
>class1 = null;
>としておけば、整理対象となり、メモリーが不足した時点にGCが整理してくれることが期待できる。さらに、
>class1 = null;
>GC.Collect();、
>としておけば、即座にリソースを解放してくれる。
>従って、わざわざclass1にIDisposeを実装するメリットは無い。
>という理解が正しいでしょうか?この認識で良いかと思います。
ただ、ガベージコレクション自体、パフォーマンスを考慮した上でチューニングされているはずですので
GC.Collect()の呼び出しに関しては、個人的には行わないほうが良いかと思います。
>> >質問2
>> >呼出例1は呼出例2に比べてメリットがありますか?
>> 現状ではClass1にはファイナライザがあるので、Disposeを呼び出してGC.SuppressFina> lize(this)を通してあげないと
>> Class1が1度のガベージコレクションでは回収されず、ヒープに残りやすくなります。
>ここが十分理解できていません。Class1を下のように定義にして常にGC.SupressFinalize()を通るようにするべきということでしょうか?
常にGC.SupressFinalizeを通るようにすること自体は、あまり意味はありません。
ファイナライザがあるのにDisposeをよばないときの動きは以下のような感じになります。
1.ファイナライザのあるオブジェクトAを生成する → Aが「ファイナライザの呼び出しが必要リスト」に登録される。
2.Aへの参照が全て無くなる ← スコープが外れるでもnullを代入でも良いです。Aを見れる人が誰もいない状態。
3.ガベージコレクションの動作開始。
3-1.マネージメモリー上の参照可能なオブジェクトを全てマークする。
3-2.マークのついていないオブジェクトの破棄を使用とする。
このとき、「ファイナライザの呼び出しが必要リスト」に登録されているオブジェクトの場合は破棄せずに、
オブジェクトを「ファイナライザの呼び出しが必要リスト」→「ファイナライザ呼び出し待ちリスト」に移し、
「ファイナライザ呼び出し待ちリスト」から参照されていることにする。(参照されていることになるため、この段階ではオブジェクトAは破棄されない)
4.ガベージコレクション終了。
5.ファイナライザ呼び出しスレッドが、「ファイナライザ呼び出し待ちリスト」に登録されているオブジェクトのファイナライザを呼ぶ。
6.ファイナライザをよんだら、「ファイナライザ呼び出し待ちリスト」から削除して、参照も切ってあげる。(ここで初めてオブジェクトAは誰からも参照されていない状態)
7.次のガベージコレクション開始。
8.Aは誰からも参照されていないので破棄できる。
このように少なくとも1回以上はガベージコレクションでの回収タイミングが遅れることになります。
さらに言えば、5.が回ってくるよりも早く次のガベージコレクションが動作した場合には、Aは参照されたままなのでさらに回収が遅れます。
これに対し、1.と2.の間でDisposeを呼び出し、その中でGC.SupressFinalizeをおこなうと、
対象のオブジェクトAを「ファイナライザの呼び出しが必要リスト」から削除できます。
ですので、3-2.の段階で「ファイナライザ呼び出し待ちリスト」に移動されること無く、破棄されることになります。
ファイナライザからの系でもGC.SupressFinalizeを呼ばれるようにしたところで、
既にガベージコレクションでの回収タイミングを逃した、上記5.のタイミングで「ファイナライザの呼び出しが必要リスト」から削除しようとするだけですので、
あまり意味はありません。
プログラミングMicrosoft .NET Framework(書籍)等をみると、上記1~8で説明したようなガベージコレクションの動作の解説が
図つきで載っていますのでわかりやすいかもしれません。もしよければ参考にどうぞ。- 回答としてマーク MicroVAX 2010年9月2日 10:06
-
また無謀にも返信してみます。
MicroVAX さん
> (2)ファイルIO、ネットワーク接続、ポインター等 → 非マネージリソース
> 例 System.Drawing.Imaging.Bitmap hugebitmap;
Ayasam さんが書かれている通り、この例の Bitmap は、マネージリソース(オブジェクト)になります。
外池さん
> A) マネージリソース「だけ」を扱うのであれば、IDisposableもデストラクタも考える必要はありません。
B で補足されていますが、やっぱりこの表現は紛らわしく感じます。
Ayasam さん
> ・float[] hugearray;など
> →これらは.Netの管理するヒープメモリ上に生成されるオブジェクト
> → いわばサンプルなどで言われるリソースではない。
ここですけど、次のページのサンプルコード
アンマネージ リソースをクリーンアップするための Finalize および Dispose の実装
http://msdn.microsoft.com/ja-jp/library/b1yfkh5e(v=VS.100).aspxに書かれている
Set large fields to null.
の対象だと思うので、リソースであることに違いはないのではないでしょうか?
それと、Dispose 不要についてですが、利用者視点で考えると、私は(非ローカルでの)null の代入は忘れるんですが(というかあまり意識しませんが)、IDispose が実装されている場合は意識して Dispose するように努めます。
もし large fields な場合には、Dispose を実装しておくと、利用者が解放の意思表示をしてくれることが少し期待できて、開発者としてはメリットにつながるのではと思いました。
話はそれますが、長い処理の前(ループ内とかも)で null にする話は以下のサイトに書かれていました。
また逆に、null 代入がだめなことも書かれていました。
(とってもパフォーマンスを気にする場合の話でしょうけど)
第 5 章 「マネージ コード パフォーマンスの向上」
http://msdn.microsoft.com/ja-jp/library/ms998547.aspx#scalenetchapt05_topic9
「所要時間の長い呼び出しをする前に不必要なメンバ変数を null に設定する」- 回答としてマーク MicroVAX 2010年9月2日 10:06
-
外池です。Takaokaさんの仰る、「IDisposable(Dispose) はリソースを解放することを目的としたメソッドではない。」は、「Dispose()を呼び出す必要があるのか?」という呼び出す側の視点の理解ではないでしょうか?
ファイナライザ、Dispose(boolean disposing)、Dispose()、これらをあるクラスに実装する側の視点からすると、IDisposableはやはりリソースを解放することを目的としたものだと思います。
いろいろ議論が尽きませんが、
http://msdn.microsoft.com/ja-jp/library/system.idisposable.dispose.aspx
や
http://msdn.microsoft.com/ja-jp/library/b1yfkh5e.aspx
の説明で、ほぼ完全に理解に達することができると思いますが・・・。
(ホームページを再開しました)- 回答としてマーク MicroVAX 2010年9月2日 10:05
-
> 「Dispose()を呼び出す必要があるのか?」という呼び出す側の視点の理解ではないでしょうか?
逆かな? クラスを作成する実装者から見ると、(前の投稿に書いたとおり) GC という制御できない場所からインスタンスが操作されることを防ぐために、IDisposable (Dispose) を実装するのですね。それは、別に Dispose ではない公開された機能であれば何でもよいのですが、あえて IDisposable インターフェースを採用するのは、クラス利用者に対して標準的な手法をみせることで、理解を助けるためですね。
リソースを解放するのは、HOW とか DO であって、WHY ではない。リソースを解放するだけであれば、Release() でも Close() でもよいのです。それをクラスデザイナからコンシューマに伝え、理解させるための手段として、フレームワークがインターフェースを1つ用意しているのです。
# だから、Close() という Dispose() のエイリアスを作成することも書かれていますよね- 回答としてマーク MicroVAX 2010年9月2日 10:05
-
すみません、すっきりしていないので返信させてもらいます。
私としては以下のように考えます。
・アンマネージリソースを直接内包する場合
→ Dispose パターンを採用すべき。
・Dispose を持つマネージリソースを内包する場合
→ Dispose を実装すべき。
・Dispose を持たないリソースを内包する場合(外池さんの狭義なもの)
→ 他の方?…Dispose は不要(価値なし)。
私だけ?…大きなメモリを使用するのであれば Dispose を実装することに価値はある。
利用者には内部で大きな配列を使っているかどうかは関係ないと思うので、利用者の null の代入にゆだねるのはあまり良いことじゃないと思います。
その点、Dispose を実装すると、「リソースを破棄してね」というクラス設計者の意思表示ができる点にメリットを感じます。
このことについて他の方のご意見があればよいのですけど。。
K.Takaoka さん
> 非常に根本的な話ですが、「IDisposable(Dispose) はリソースを解放することを目的としたメソッドではない。」という認識はありますでしょうか?
K.Takaoka さんはそう認識されていないということでしょうか?
私はそれが目的だという認識です。
それと、K.Takaoka さんが書かれた後半部分は、あくまで良い応用例にすぎず、MicroVAX さんのご質問の話から少しずれていることのように思いました。
ところで、資料を読んでいて思い出しましたが、以前、MicroVAX さんがコネクトにて提案されていた以下の件は、もともとそうなっていると言えますね。
使用されないことが明白なローカル変数をGCの回収対象含める
http://connect.microsoft.com/VisualStudioJapan/feedback/details/427520/-gc#details- 回答としてマーク MicroVAX 2010年9月2日 10:05
-
外池です。MicroVAXさんの(6)の用語の定義に従うことにします。あと「フィールドにリソースを持つ」とは「メンバ変数でオブジェクトへの参照を保持する(値型の変数はここでは考えない)」と言う意味に理解しています。
さて、(1)は、私もそのとおりに理解しています。(6)の「通常リソースとは言わない。」に相当
(2)も、同じ理解です。ただ、あえて細かいことを言えば、(6)のマネージドリソースしかない場合は、Disposeだけがあればよく、ファイナライザは不要だと理解しています。(6)の非マネージリソースがある場合は、ファイナライザも必要です。しかし、どちらの場合にも適用可能な1つのデザインパターンを一貫して用いることは、良いことだと思います。
(3)は、私も同じ理解です。
(4)は、(3)とも関係しますが、ロジックの上流のほうで大きなリソースを使って不要になって、ロジックの下流でまた大きなリソースを確保する必要が出る、という想定をするならば、やはり、要らないものはnullにしておくのが良いかと思います。
(5)は、OK
(6)は飛ばして、
(7)もOKです。
このスレッドのおかげで、私も、相当に理解・整理が出来ました。
(ホームページを再開しました)- 回答としてマーク MicroVAX 2010年9月2日 10:05
-
DataTable は MarshalByValueComponent から継承しているために Dispose を持っていますが、DataTable としては何も実装していなかったりします。
え、そうなのですかっ?!それは初耳ですが、それってソースあります??(@@;)
ひらぽん http://d.hatena.ne.jp/hilapon/
DataTable.Dispose
http://msdn.microsoft.com/ja-jp/library/system.data.datatable.dispose.aspx
文字通りのソースコードもありますが、DataTableはDisposeメソッドをオーバーライドしていません。
MarshalByValueComponentクラスのDisposeメソッドは実装があり、Siteプロパティ、Eventsプロパティが使用されている場合にそれらの後始末をしているようです。
#完全に空の実装というわけではないですし、
#上記の詳細の話はあくまで実装レベルでの話なので、
#Disposeメソッドを呼んでおくことに越したことはないと思います。- 回答としてマーク MicroVAX 2014年11月28日 4:31
-
外池です。
「マネージリソース」は、狭義に、「純粋にマネージリソースしか使っていない」の意味で用いていました。TH01さんの仰るとおりで、マネージドなオブジェクトにも、ファイルアクセス、ネット通信、データベース接続などの機能があることは承知していますが、そういうものを「非マネージリソースを内包(ラップ)している」と表現しました。
実際、C#ならば、非マネージリソースが生の形で現れることは、ハンドルを保持する変数ぐらいですよね。すべてのオブジェクトはマネージドな外観をしていて、その中に非マネージドなリソースを使っているものもあるわけです。マネージドなオブジェクトのメンバー変数に、マネージドなオブジェクトが保持されていて、さらにそのメンバーの・・・、という入れ子でどこかで非マネージリソースが使われていたら、「非マネージリソースを内包(ラップ)している」と表現しているとご理解ください。
(ホームページを再開しました)- 回答としてマーク MicroVAX 2010年9月2日 10:07
-
あ、失礼しました。
狭義であることを外池さんは確認されていますし、そうでなければ質問もされなかったかもしれないですからね。
それと、ローカル変数への null 代入についても指摘されていましたね。
狭義の意味でもメリットはあると思ったのですが、まだ十分に把握できていなくてわからなくなりました。
以下の解釈でメリットはないということでしょうか?
Dispose を実装しなくても・・・
↓
ローカル変数であれば不要になった時点、そうでなければ null 代入時点で GC 候補になる。
(あ、今気付きましたが、長いループ内だと、ローカル変数でも null 代入の効果はあるはずですよね)
↓
GC の時点で内包しているプリミティブなメンバの領域も解放される。
↓
なので実装してもしなくても同じ。
今回も割り込み質問になってしまい、本当にごめんなさい。
追記:
ちなみに言い訳ですが、マネージリソースであることを念を押すために
> つまり両方とも(Class は直接的に)非マネージリソースは内包していないということで?
(カッコ内は私が補いました)
と読んでしまい、狭義な話の確認だと気づけませんでした。
例題が狭義なものになっているのに、ただの一例だと考えてしまいました。 -
MicroVAXさん
的外れだったら、ごめんなさい。先に謝っておきます。
過去の質問(http://social.msdn.microsoft.com/Forums/ja-JP/csharpgeneralja/thread/dddc5b82-6c8e-43aa-b43e-91a6759cf44a/#4971a648-cc34-4957-8d13-2db90e003c2a)
をみて気になったのですが、マネージリソースと非マネージリソースの捕らえ方が少し気になりました。
もしかして、MicroVAXさんの認識は以下のような感じになっていませんか?
Disposeを搭載していないもの → マネージリソース
Disposeを搭載しているもの → 非マネージリソース
私の思う一般的な認識としては
Disposeを搭載していないもの → マネージオブジェクト
Disposeを搭載しているもの → マネージリソース (C++等のハンドル/ポインタとかをどこかに内包しているもの)
C++等のハンドル/ポインタとか直のもの → 非マネージリソース
・私の思う一般認識が正しいこと
・MicroVAXさんとの認識が異なっていること
を前提に回答をすると
>質問1
>Class1のようにマネージリソースのみで構成されているクラスの場合、わざわざIDisposeを実装するメリットはありますか?
Class1はメンバーに抱えているものが、マネージリソースではなくただのマネージオブジェクトなので開放の必要がありません。
#_bigarrayはただのdoubleですよね?
ですので、そもそもDisposeの実装は必要ありません。>質問2
>呼出例1は呼出例2に比べてメリットがありますか?
現状ではClass1にはファイナライザがあるので、Disposeを呼び出してGC.SuppressFinalize(this)を通してあげないと
Class1が1度のガベージコレクションでは回収されず、ヒープに残りやすくなります。
現状のコードのままであれば、呼出例1にメリットがあります。
-
全ての返信に目を通せていませんが、ちょっと引っかかった点が・・・(6)リソースという用語は例えば次のように使われる。
・double[],string等→通常リソースとは言わない。
・Stream,Bitmap等→マネージドリソース。
・ポインター等→非マネージリソースStream, Bitmap 等を 「マネージドリソース」 と呼んでますが、これは 「アンマネージドリソース」 ではないでしょうか?
アンマネージリソースを 「カプセルする」 クラスは、マネージドクラスであっても、呼び出し側からは 「アンマネージドリソース」 と見なすべきだと思いますが。マネージリソースとアンマネージリソース
アンマネージリソース と Dispose と GC の関係について
ひらぽん http://d.hatena.ne.jp/hilapon/- 回答としてマーク MicroVAX 2010年9月2日 10:04
すべての返信
-
外池です。すいません、確認ですが、質問1と質問2のclass1は同じものですよね?つまり両方とも非マネージリソースは内包していないということで? その前提で、
質問1については、IDisposableを実装するメリットは無いです。したがって、質問2については、呼出例2で十分です。
-------
なお、プログラムの可読性は別にして、動作の観点だけからコメントしますと、
呼出例1ですが、usingでclass1を明示しつつclass1.Disposeを呼ぶのは冗長です。usingのブロックを抜けるときに自動的にDisposeが呼ばれるからです。呼出例2についても、class1=nullの代入も冗長です。
(ホームページを再開しました)- 回答としてマーク MicroVAX 2010年9月2日 10:08
-
あれ?私は意見が異なります。
※いつものように私が間違っているのかも?
IDisposable を実装する目的は、
マネージかアンマネージかにかかわらず、
任意の時点で即座にリソースを解放する手段を提供することになります。
呼出例1では、Dispose の実装内容によってそのタイミングでリソースを解放できますが、
呼出例2では、いつ実行されるかわからない GC に頼ることになります。
例題の Class1 では外池さんが書かれた通りかと思いますが、マネージリソース全般に当てはまるわけではないと思います。
たとえばサーバーへの接続を保持するようなクラスを包含するクラスでは、IDisposable を実装して包含オブジェクトの Dispose を実行するようにすることで、開発者が接続期間を制御できるようになります。
もちろん、別の手段でその機能を実現してもいいですが、包含する複数のリソースを解放するための「動詞」は様々かもしれないため、リソース全般のための Dispose が最適だと思います。
なお、アンマネージリソースを保持していない場合にもデストラクタを実装すべきかどうかについては、
別スレッドの
http://social.msdn.microsoft.com/Forums/ja-JP/netfxgeneralja/thread/6b3f4379-2b3b-475e-9833-ee37b962491e
にて、意見を書かせてもらいました。 -
それと、null を代入することの意義については、こちらも参考になると思います。
Dispose()とnull代入
http://social.msdn.microsoft.com/Forums/ja-JP/vsgeneralja/thread/65c3c5fe-036a-457b-9d6a-0e418f0f4770
追記:
書かれた呼出例2の場合、null を代入すること自体に、.NET としては意味がありません。
(と、上記スレッドで学びました。) -
.NET のオブジェクトとしてのメリットはありませんが、多くの .NET 対応のプログラム言語が IDisposable に対する便利な構文をサポートしており、それを享受できるというメリットがあります。C# では using ブロックがあり、
public class WaitCursor : IDisposable { public WaitCursor() { this.OldCursor = Screen.Cursor; Screen.Cursor = Cursors.Wait; } private Cursor OldCursor; private IDIsposable.Dispose() { Screen.Cursor = this.OldCursor; } }
こんなマネジドクラスなら、
using (new WaitCurosor()) { /* 時間のかかる処理*/ }
みたいな使い方ができます。
- 回答としてマーク MicroVAX 2010年9月2日 10:07
-
外池です。
「マネージリソース」は、狭義に、「純粋にマネージリソースしか使っていない」の意味で用いていました。TH01さんの仰るとおりで、マネージドなオブジェクトにも、ファイルアクセス、ネット通信、データベース接続などの機能があることは承知していますが、そういうものを「非マネージリソースを内包(ラップ)している」と表現しました。
実際、C#ならば、非マネージリソースが生の形で現れることは、ハンドルを保持する変数ぐらいですよね。すべてのオブジェクトはマネージドな外観をしていて、その中に非マネージドなリソースを使っているものもあるわけです。マネージドなオブジェクトのメンバー変数に、マネージドなオブジェクトが保持されていて、さらにそのメンバーの・・・、という入れ子でどこかで非マネージリソースが使われていたら、「非マネージリソースを内包(ラップ)している」と表現しているとご理解ください。
(ホームページを再開しました)- 回答としてマーク MicroVAX 2010年9月2日 10:07
-
あ、失礼しました。
狭義であることを外池さんは確認されていますし、そうでなければ質問もされなかったかもしれないですからね。
それと、ローカル変数への null 代入についても指摘されていましたね。
狭義の意味でもメリットはあると思ったのですが、まだ十分に把握できていなくてわからなくなりました。
以下の解釈でメリットはないということでしょうか?
Dispose を実装しなくても・・・
↓
ローカル変数であれば不要になった時点、そうでなければ null 代入時点で GC 候補になる。
(あ、今気付きましたが、長いループ内だと、ローカル変数でも null 代入の効果はあるはずですよね)
↓
GC の時点で内包しているプリミティブなメンバの領域も解放される。
↓
なので実装してもしなくても同じ。
今回も割り込み質問になってしまい、本当にごめんなさい。
追記:
ちなみに言い訳ですが、マネージリソースであることを念を押すために
> つまり両方とも(Class は直接的に)非マネージリソースは内包していないということで?
(カッコ内は私が補いました)
と読んでしまい、狭義な話の確認だと気づけませんでした。
例題が狭義なものになっているのに、ただの一例だと考えてしまいました。 -
外池です。
MicroVAXさんのお示しになった例は、マネージドリソースだけを着目したときのDisposeのメリットの有無やGCのタイミングと効能を考える上で、良い例だと思います。
私は、TH01さんとまったく同じ理解をしています。
マネージドな配列や、代入したり、繋いだり、切ったりしまくったstringの断片たちのように、一見、ローカル変数で使っているように見えても、実際はヒープに入っているオブジェクトが今の話題の対象ですよね? で、変数にnullを代入したり、あるいは、ローカル変数がスコープから外れて、ヒープにある本体データへの参照が切れてしまったら、あとはGCを待つしかありません。クラスのメンバー変数で参照する場合で、IDisposableを実装してそこでnullを代入したとしても、やはりあとはGC待ちしかない、という点で、事情は同じです。
GCが何回か繰り返される間、ずっとスコープ内にとどまるような寿命の長い変数が参照するオブジェクトで大きなリソースであれば、TH01さんの仰るとおり、無用になればnullを代入しておくことが、メリットがあると思います。当たり前ですが本当に全体リソースが逼迫したときは、無用なものはnullにしておかないとGCしてもらえなくてパンクしちゃいます。
(ホームページを再開しました)- 回答としてマーク MicroVAX 2010年9月2日 10:07
-
MicroVAXさん
的外れだったら、ごめんなさい。先に謝っておきます。
過去の質問(http://social.msdn.microsoft.com/Forums/ja-JP/csharpgeneralja/thread/dddc5b82-6c8e-43aa-b43e-91a6759cf44a/#4971a648-cc34-4957-8d13-2db90e003c2a)
をみて気になったのですが、マネージリソースと非マネージリソースの捕らえ方が少し気になりました。
もしかして、MicroVAXさんの認識は以下のような感じになっていませんか?
Disposeを搭載していないもの → マネージリソース
Disposeを搭載しているもの → 非マネージリソース
私の思う一般的な認識としては
Disposeを搭載していないもの → マネージオブジェクト
Disposeを搭載しているもの → マネージリソース (C++等のハンドル/ポインタとかをどこかに内包しているもの)
C++等のハンドル/ポインタとか直のもの → 非マネージリソース
・私の思う一般認識が正しいこと
・MicroVAXさんとの認識が異なっていること
を前提に回答をすると
>質問1
>Class1のようにマネージリソースのみで構成されているクラスの場合、わざわざIDisposeを実装するメリットはありますか?
Class1はメンバーに抱えているものが、マネージリソースではなくただのマネージオブジェクトなので開放の必要がありません。
#_bigarrayはただのdoubleですよね?
ですので、そもそもDisposeの実装は必要ありません。>質問2
>呼出例1は呼出例2に比べてメリットがありますか?
現状ではClass1にはファイナライザがあるので、Disposeを呼び出してGC.SuppressFinalize(this)を通してあげないと
Class1が1度のガベージコレクションでは回収されず、ヒープに残りやすくなります。
現状のコードのままであれば、呼出例1にメリットがあります。
-
アドバイスありがとうございます。
質問の背景
数値計算をしています。計算の途中で巨大なint[]やfloat[]をメンバーに含む複数のオブジェクトを作成します。メモリーを節約するためスコープ途中でもリソースを解放しようと考えました。ですから、質問1と2はClass1は下の(1)を想定していました。(1)数値データ等を保存しているクラス → マネージリソース
例 float[] hugearray;
(2)ファイルIO、ネットワーク接続、ポインター等 → 非マネージリソース
例 System.Drawing.Imaging.Bitmap hugebitmap;
Class1のような場合
class1 = null;
としておけば、整理対象となり、メモリーが不足した時点にGCが整理してくれることが期待できる。さらに、
class1 = null;
GC.Collect();、
としておけば、即座にリソースを解放してくれる。
従って、わざわざclass1にIDisposeを実装するメリットは無い。
という理解が正しいでしょうか?
> >質問2
> >呼出例1は呼出例2に比べてメリットがありますか?
> 現状ではClass1にはファイナライザがあるので、Disposeを呼び出してGC.SuppressFina> lize(this)を通してあげないと
> Class1が1度のガベージコレクションでは回収されず、ヒープに残りやすくなります。
ここが十分理解できていません。Class1を下のように定義にして常にGC.SupressFinalize()を通るようにするべきということでしょうか?
// Class1の定義public sealed class Class1 : IDisposable
{public Class1()
{
_bigarray = new double[100000];
Random rnd = new Random();
for (int index = 0; index < _bigarray.Length; index++)
{
_bigarray[index] = rnd.NextDouble();
}
}~Class1()
{
Dispose(false);
}public double GetAverage() { return _bigarray.Average(); }
public void Dispose()
{
Dispose(true);
}private void Dispose(bool disposing)
{
// スレッドセーフにします。
lock(this)
{
if (disposing)
{
// ここでマネージリソースを破棄します。
if (_bigarray != null) _bigarray = null;
}
// ここで非マネージリソースを解放します。
GC.SupressFinalize(this);
}
}private double[] _bigarray;
}
宜しくお願いします。
C#開発者 -
外池です。マネージドな配列やマネージドな値型の変数ばかりを使って数値計算をするような状況であれば、IDisposableの実装は要らないです。マネージドな配列が不要になれば、すべての参照を断ち切る(ある不要になった配列を参照しているスコープ内の変数すべてにnullを代入する)ことができれば、自動的にGCされます。(最初の太字下線部については、そのとおりです。)
強制的にGCすることが良いかどうかは、物理メモリーの搭載量、ページファイルの最大サイズ、あと、プログラムでどのように配列を使っているのか(必要になるタイミング、必要な量、不要になるタイミング)によりますので、なんとも言えません。
太字2番目の2つ目のご質問ですが、GCの動作について背景知識が必要になります。あるオブジェクトがどこからも参照されていない、つまり不要であることを検知する動作と、デストラクタを呼び出す動作と、最終的に占有していたメモリーを解放する動作がどのような段階を踏むかという問題です。私も詳しくないのですが・・・、大体、以下のような感じです。
1) あるデストラクタが存在するオブジェクトへの参照が無くなった場合ですが、GCの1回の動作で不要であることが検知されて一旦終わってしまいます。次のGCの動作においてデストラクタが実行されて占有していたメモリーが解放されます。
2) デストラクタが存在しないオブジェクトへの参照が無くなった場合は、GCの1回の動作で不要であることが検知されて、すぐに占有していたメモリーが解放されます。
SupressFinalizeを使うと、デストラクタがあるオブジェクトでも2)の動作を強制できます。
このあたりは、IDisposableのドキュメント、GCのドキュメントをじっくり読むことをお奨めしますし、デザインパターンはキチっと守るのが良いと思います。
繰り返しになりますが、
A) マネージリソース「だけ」を扱うのであれば、IDisposableもデストラクタも考える必要はありません。
B) 一見マネージドなオブジェクトに見えても非マネージリソースを扱っていて、IDisposableが実装されているオブジェクトがあります。このようなオブジェクトを内包するオブジェクトをプログラムされる際には、やはりIDisposableを実装すべきですが、public void Dispose()と、private void Dispoas(bool disposing)のうちdisposing==trueの処理があれば足ります。デストラクタは不要なはずです。
C) 作ろうとしているオブジェクトが非マネージリソースを直接扱う場合、例えば、非マネージリソースを確保してそのハンドルを保持するような場合ですが、IDisposableをすべて実装して、デストラクタも必要になります。このオブジェクトが不要になるごとにDisposeを必ず呼び出してもらえるならデストラクタは要らないのですが、Disposeを忘れてしまった場合でも、GCの際にデストラクタを呼び出してもらえるので安全です。
(ホームページを再開しました)- 回答としてマーク MicroVAX 2010年9月2日 10:06
-
To:MicroVAXさん
見直してみましたが、MSDNにあるDisposeパターンのサンプルなどで書かれているものと、
MicroVAXさんのもっている「リソース」の認識がずれているような気がします。
・float[] hugearray;など
→これらは.Netの管理するヒープメモリ上に生成されるオブジェクト
→ いわばサンプルなどで言われるリソースではない。
・ファイルIO、ネットワーク接続、ポインター等
→ これらをリソースとよんでいるはずです。
その上で、上記のようなリソースを内応するマネージオブジェクトをマネージリソースとよんでいると思います。
→例: System.Drawing.Imaging.Bitmap hugebitmap;など
非マネージリソースは?
→ C++の関数をインポートして取得したリソースのIntPtr、unsafeで取得してきたリソースのIntPtrなど
と思います。
ということで、例に挙がっているClass1は、そもそもリソースを保持していないので、Disposeパターンの実装の必要が無いと思います。
ですので、
>Class1のような場合
>class1 = null;
>としておけば、整理対象となり、メモリーが不足した時点にGCが整理してくれることが期待できる。さらに、
>class1 = null;
>GC.Collect();、
>としておけば、即座にリソースを解放してくれる。
>従って、わざわざclass1にIDisposeを実装するメリットは無い。
>という理解が正しいでしょうか?この認識で良いかと思います。
ただ、ガベージコレクション自体、パフォーマンスを考慮した上でチューニングされているはずですので
GC.Collect()の呼び出しに関しては、個人的には行わないほうが良いかと思います。
>> >質問2
>> >呼出例1は呼出例2に比べてメリットがありますか?
>> 現状ではClass1にはファイナライザがあるので、Disposeを呼び出してGC.SuppressFina> lize(this)を通してあげないと
>> Class1が1度のガベージコレクションでは回収されず、ヒープに残りやすくなります。
>ここが十分理解できていません。Class1を下のように定義にして常にGC.SupressFinalize()を通るようにするべきということでしょうか?
常にGC.SupressFinalizeを通るようにすること自体は、あまり意味はありません。
ファイナライザがあるのにDisposeをよばないときの動きは以下のような感じになります。
1.ファイナライザのあるオブジェクトAを生成する → Aが「ファイナライザの呼び出しが必要リスト」に登録される。
2.Aへの参照が全て無くなる ← スコープが外れるでもnullを代入でも良いです。Aを見れる人が誰もいない状態。
3.ガベージコレクションの動作開始。
3-1.マネージメモリー上の参照可能なオブジェクトを全てマークする。
3-2.マークのついていないオブジェクトの破棄を使用とする。
このとき、「ファイナライザの呼び出しが必要リスト」に登録されているオブジェクトの場合は破棄せずに、
オブジェクトを「ファイナライザの呼び出しが必要リスト」→「ファイナライザ呼び出し待ちリスト」に移し、
「ファイナライザ呼び出し待ちリスト」から参照されていることにする。(参照されていることになるため、この段階ではオブジェクトAは破棄されない)
4.ガベージコレクション終了。
5.ファイナライザ呼び出しスレッドが、「ファイナライザ呼び出し待ちリスト」に登録されているオブジェクトのファイナライザを呼ぶ。
6.ファイナライザをよんだら、「ファイナライザ呼び出し待ちリスト」から削除して、参照も切ってあげる。(ここで初めてオブジェクトAは誰からも参照されていない状態)
7.次のガベージコレクション開始。
8.Aは誰からも参照されていないので破棄できる。
このように少なくとも1回以上はガベージコレクションでの回収タイミングが遅れることになります。
さらに言えば、5.が回ってくるよりも早く次のガベージコレクションが動作した場合には、Aは参照されたままなのでさらに回収が遅れます。
これに対し、1.と2.の間でDisposeを呼び出し、その中でGC.SupressFinalizeをおこなうと、
対象のオブジェクトAを「ファイナライザの呼び出しが必要リスト」から削除できます。
ですので、3-2.の段階で「ファイナライザ呼び出し待ちリスト」に移動されること無く、破棄されることになります。
ファイナライザからの系でもGC.SupressFinalizeを呼ばれるようにしたところで、
既にガベージコレクションでの回収タイミングを逃した、上記5.のタイミングで「ファイナライザの呼び出しが必要リスト」から削除しようとするだけですので、
あまり意味はありません。
プログラミングMicrosoft .NET Framework(書籍)等をみると、上記1~8で説明したようなガベージコレクションの動作の解説が
図つきで載っていますのでわかりやすいかもしれません。もしよければ参考にどうぞ。- 回答としてマーク MicroVAX 2010年9月2日 10:06
-
また無謀にも返信してみます。
MicroVAX さん
> (2)ファイルIO、ネットワーク接続、ポインター等 → 非マネージリソース
> 例 System.Drawing.Imaging.Bitmap hugebitmap;
Ayasam さんが書かれている通り、この例の Bitmap は、マネージリソース(オブジェクト)になります。
外池さん
> A) マネージリソース「だけ」を扱うのであれば、IDisposableもデストラクタも考える必要はありません。
B で補足されていますが、やっぱりこの表現は紛らわしく感じます。
Ayasam さん
> ・float[] hugearray;など
> →これらは.Netの管理するヒープメモリ上に生成されるオブジェクト
> → いわばサンプルなどで言われるリソースではない。
ここですけど、次のページのサンプルコード
アンマネージ リソースをクリーンアップするための Finalize および Dispose の実装
http://msdn.microsoft.com/ja-jp/library/b1yfkh5e(v=VS.100).aspxに書かれている
Set large fields to null.
の対象だと思うので、リソースであることに違いはないのではないでしょうか?
それと、Dispose 不要についてですが、利用者視点で考えると、私は(非ローカルでの)null の代入は忘れるんですが(というかあまり意識しませんが)、IDispose が実装されている場合は意識して Dispose するように努めます。
もし large fields な場合には、Dispose を実装しておくと、利用者が解放の意思表示をしてくれることが少し期待できて、開発者としてはメリットにつながるのではと思いました。
話はそれますが、長い処理の前(ループ内とかも)で null にする話は以下のサイトに書かれていました。
また逆に、null 代入がだめなことも書かれていました。
(とってもパフォーマンスを気にする場合の話でしょうけど)
第 5 章 「マネージ コード パフォーマンスの向上」
http://msdn.microsoft.com/ja-jp/library/ms998547.aspx#scalenetchapt05_topic9
「所要時間の長い呼び出しをする前に不必要なメンバ変数を null に設定する」- 回答としてマーク MicroVAX 2010年9月2日 10:06
-
非常に根本的な話ですが、「IDisposable(Dispose) はリソースを解放することを目的としたメソッドではない。」という認識はありますでしょうか?
単純に「プログラマが任意のタイミングでオブジェクトの状態を(破棄状態へ)変化させるための機能」を統一的に提供するための定義であることが役割であって、その実装の代表例がリソースの解放ですよね。リソースの解放とセットで語られることが多いですが、本来の目的は、オブジェクトの回収/破棄が管理されたマネジドな世界において「GC によるオブジェクト回収」というタイミングや実行コンテキストの制御が難しい部分から、手軽に抜け出すためのものでしかないと思います。リソースの解放を行うことを主目的とせず、オブジェクトの役割として状態変化を意図した IDisposable の実装というのは、クラスライブラリよりもアプリケーションの実装のほうで良く見ますが、そんなに珍しい用法だとは思いません。System.Transaction ネームスペースの TransactionScope なんかは、内部的にはアンマネージリソースも保持しているようなものでしょうけれど、クラスデザインとしてはリソースの破棄をするために IDisposable を実装しているというよりも、スコープの終了をプログラマが明示するために IDisposable を実装していると思います。(そして、その都合で using と相性がよい)
- 回答としてマーク MicroVAX 2010年9月2日 10:06
-
外池です。Takaokaさんの仰る、「IDisposable(Dispose) はリソースを解放することを目的としたメソッドではない。」は、「Dispose()を呼び出す必要があるのか?」という呼び出す側の視点の理解ではないでしょうか?
ファイナライザ、Dispose(boolean disposing)、Dispose()、これらをあるクラスに実装する側の視点からすると、IDisposableはやはりリソースを解放することを目的としたものだと思います。
いろいろ議論が尽きませんが、
http://msdn.microsoft.com/ja-jp/library/system.idisposable.dispose.aspx
や
http://msdn.microsoft.com/ja-jp/library/b1yfkh5e.aspx
の説明で、ほぼ完全に理解に達することができると思いますが・・・。
(ホームページを再開しました)- 回答としてマーク MicroVAX 2010年9月2日 10:05
-
> 「Dispose()を呼び出す必要があるのか?」という呼び出す側の視点の理解ではないでしょうか?
逆かな? クラスを作成する実装者から見ると、(前の投稿に書いたとおり) GC という制御できない場所からインスタンスが操作されることを防ぐために、IDisposable (Dispose) を実装するのですね。それは、別に Dispose ではない公開された機能であれば何でもよいのですが、あえて IDisposable インターフェースを採用するのは、クラス利用者に対して標準的な手法をみせることで、理解を助けるためですね。
リソースを解放するのは、HOW とか DO であって、WHY ではない。リソースを解放するだけであれば、Release() でも Close() でもよいのです。それをクラスデザイナからコンシューマに伝え、理解させるための手段として、フレームワークがインターフェースを1つ用意しているのです。
# だから、Close() という Dispose() のエイリアスを作成することも書かれていますよね- 回答としてマーク MicroVAX 2010年9月2日 10:05
-
すみません、すっきりしていないので返信させてもらいます。
私としては以下のように考えます。
・アンマネージリソースを直接内包する場合
→ Dispose パターンを採用すべき。
・Dispose を持つマネージリソースを内包する場合
→ Dispose を実装すべき。
・Dispose を持たないリソースを内包する場合(外池さんの狭義なもの)
→ 他の方?…Dispose は不要(価値なし)。
私だけ?…大きなメモリを使用するのであれば Dispose を実装することに価値はある。
利用者には内部で大きな配列を使っているかどうかは関係ないと思うので、利用者の null の代入にゆだねるのはあまり良いことじゃないと思います。
その点、Dispose を実装すると、「リソースを破棄してね」というクラス設計者の意思表示ができる点にメリットを感じます。
このことについて他の方のご意見があればよいのですけど。。
K.Takaoka さん
> 非常に根本的な話ですが、「IDisposable(Dispose) はリソースを解放することを目的としたメソッドではない。」という認識はありますでしょうか?
K.Takaoka さんはそう認識されていないということでしょうか?
私はそれが目的だという認識です。
それと、K.Takaoka さんが書かれた後半部分は、あくまで良い応用例にすぎず、MicroVAX さんのご質問の話から少しずれていることのように思いました。
ところで、資料を読んでいて思い出しましたが、以前、MicroVAX さんがコネクトにて提案されていた以下の件は、もともとそうなっていると言えますね。
使用されないことが明白なローカル変数をGCの回収対象含める
http://connect.microsoft.com/VisualStudioJapan/feedback/details/427520/-gc#details- 回答としてマーク MicroVAX 2010年9月2日 10:05
-
ご議論いただきありがとうございます。
この議論を通してGC.SupressFinalize()について理解を深めることができました。理解度を確かめるために自分なりに要点をまとめてみました。もし間違いがあればご指摘願えればありがたいです。
要点
(1)フィールドにリソースを持たないクラスはDispose()もファイナライザ(デストラクタ)も実装する必要が無い。そのクラスはファイナライザが実装されていないため、どこからも参照されなくなった時点で即座にGC対象になるからである。例題のClass1はこのケースに相当する。
(2)フィールドにリソースを持つクラスはDispose()とファイナライザ(デストラクタ)をセットで記述する。明示的にDispose()を呼び出した場合はGC.SupressFinalize(this)を通してファイナライザの待ち候補にならないようにする。
(3)フィールドは不要になった時点で破棄(Dispose()あるいはnull代入)して早めに参照をなくすことで省メモリが期待できる。
(4)上から下に流れるようなロジックの場合、途中でローカル変数にnull代入する意味は無い。ローカル変数はロジックの最下部ではなくて最後に参照された時点でGC対象になるからである。
(5)長いループの場合、ループ途中でローカル変数にnull代入する意味はある。GC対象になるのが早まるからである。
(6)リソースという用語は例えば次のように使われる。
・double[],string等→通常リソースとは言わない。
・Stream,Bitmap等→マネージドリソース。
・ポインター等→非マネージリソース
(7)Dispose()を実装する意味は任意のタイミングでリソースを解放できることを標準化された表現でユーザーに伝える意味がある。
C#開発者 -
外池です。MicroVAXさんの(6)の用語の定義に従うことにします。あと「フィールドにリソースを持つ」とは「メンバ変数でオブジェクトへの参照を保持する(値型の変数はここでは考えない)」と言う意味に理解しています。
さて、(1)は、私もそのとおりに理解しています。(6)の「通常リソースとは言わない。」に相当
(2)も、同じ理解です。ただ、あえて細かいことを言えば、(6)のマネージドリソースしかない場合は、Disposeだけがあればよく、ファイナライザは不要だと理解しています。(6)の非マネージリソースがある場合は、ファイナライザも必要です。しかし、どちらの場合にも適用可能な1つのデザインパターンを一貫して用いることは、良いことだと思います。
(3)は、私も同じ理解です。
(4)は、(3)とも関係しますが、ロジックの上流のほうで大きなリソースを使って不要になって、ロジックの下流でまた大きなリソースを確保する必要が出る、という想定をするならば、やはり、要らないものはnullにしておくのが良いかと思います。
(5)は、OK
(6)は飛ばして、
(7)もOKです。
このスレッドのおかげで、私も、相当に理解・整理が出来ました。
(ホームページを再開しました)- 回答としてマーク MicroVAX 2010年9月2日 10:05
-
全ての返信に目を通せていませんが、ちょっと引っかかった点が・・・(6)リソースという用語は例えば次のように使われる。
・double[],string等→通常リソースとは言わない。
・Stream,Bitmap等→マネージドリソース。
・ポインター等→非マネージリソースStream, Bitmap 等を 「マネージドリソース」 と呼んでますが、これは 「アンマネージドリソース」 ではないでしょうか?
アンマネージリソースを 「カプセルする」 クラスは、マネージドクラスであっても、呼び出し側からは 「アンマネージドリソース」 と見なすべきだと思いますが。マネージリソースとアンマネージリソース
アンマネージリソース と Dispose と GC の関係について
ひらぽん http://d.hatena.ne.jp/hilapon/- 回答としてマーク MicroVAX 2010年9月2日 10:04
-
(4) があるので本質的なところは理解されていることと思いますが (3) は概ね間違いですね。
(4) にあるように、ローカル変数が参照を失った時点で GC の対象になるのと同じで、オブジェクトが保持するフィールドについても、null を明示的に代入することがなくとも、フィールドを保持するクラス インスタンスへの参照が無くなった時点で同時に GC の対象となります。
早めに Dispose() や null の代入によって参照をなくすことでメモリ回収を手助けできるのは、そのフィールドを保持するオブジェクトが生きて使われ続ける場合のみに限定されます。このため、一般的な Dispose() の実装で自身のフィールドに null を代入することによって GC の挙動はほぼ変化しません。
最初の投稿の class1 のように、ただ null をチェックして代入するだけの内容の Dispose() が、そのインスタンスに対する最後の操作であった場合、逆に Dispose 内からメンバに触れることで「何もしないDispose」と比較して、this 自身の回収がすこしだけ遅れ、ほんのわずかな時間だけメモリを多く使うことが偶発的に発生しうるんじゃないかな。
-
MicroVAX さんへ
(1) については私の考えは異なります。
前述の通り、「どこからも参照されなくなった時点で」という前提を満たすために Dispose を実装した方がよい場合もあると考えるためです。
極端な具体例としては、1Gの配列を保持するクラスを、ユーザーの null 代入にゆだねるのはメモリリソースの消費量からみた場合に問題があると感じます(ローカルでしか使われないという想定はもちろんしていません)。
他の方は null が代入されること(もしくはローカル使用のみ)が前提になっていますので、その点が私と異なっています。
私の主張に対するご意見がないのが残念です。
ひらぽんさん
> これは 「アンマネージドリソース」 ではないでしょうか?
それは違うと断言できます。(^^;
ひらぽんさんご提示の1つ目のリンク先の表現をすると、Stream や Bitmap が内包しているアンマネージリソースはすでに GC の管理下にあります。
K.Takaoka さん
> (3) は概ね間違いですね。
私はそうは思いませんでした。
ただ、その後に書かれていることは、MicorVAX さんを含め、コンセンサスが得られていることだと感じます。
「フィールドを保持するクラス インスタンスへの参照が無くなった時点」を前提とした話か、それより前も含めた話かの違いに思います。 -
ひらぽんさん
> これは 「アンマネージドリソース」 ではないでしょうか?
それは違うと断言できます。(^^;
ひらぽんさんご提示の1つ目のリンク先の表現をすると、Stream や Bitmap が内包しているアンマネージリソースはすでに GC の管理下にあります。
う~ん・・・どうにも釈然としないので、色々調べてみました。http://msdn.microsoft.com/ja-jp/library/fs2xkftw.aspx
ここを読む限りでは、
(6)リソースという用語は例えば次のように使われる。
・double[],string等→通常リソースとは言わない。
ではなく、配列を 「マネージリソース」 と呼んでますね。また「Stream, Bitmap」 等は厳密には 「リソース」 ではなく、 「アンマネージリソースをカプセル化したクラス」 と認識するのが正解なんだと思いました。ややこしいですね(^^;
http://msdn.microsoft.com/ja-jp/library/ms143450.aspx
#いずれにしても明示的に解放しますがね。
ひらぽん http://d.hatena.ne.jp/hilapon/ -
私の引用の後に Ayasam さんの引用をされていますが、
その Ayasam さんの表現も少し違うと思ったので、私も指摘させていただきました。
ただし、Dispose の話では「通常」は解放対象には含めませんので、別に間違っているわけではないとも考えます。
けど、皆さんと異なる今回の私の主張は、「通常」に含まれない場合のことになります。
> 「アンマネージリソースをカプセル化したクラス」 と認識するのが正解なんだと思いました。
この点は、Ayasam さんが書かれた
「その上で、上記のようなリソースを内応するマネージオブジェクトをマネージリソースとよんでいると思います。」
の表現がわかりやすく、一般的だと思います。
表現はいろいろあるかもしれませんが、少なくとも「アンマネージドリソース」と言うと真逆になってしまいますよね。
ファイナライザを実装して呼ばれるようにするかどうかに関係しますので、区別は重要です。 -
> 「アンマネージリソースをカプセル化したクラス」 と認識するのが正解なんだと思いました。
この点は、Ayasam さんが書かれた
「その上で、上記のようなリソースを内応するマネージオブジェクトをマネージリソースとよんでいると思います。」
の表現がわかりやすく、一般的だと思います。
表現はいろいろあるかもしれませんが、少なくとも「アンマネージドリソース」と言うと真逆になってしまいますよね。
ファイナライザを実装して呼ばれるようにするかどうかに関係しますので、区別は重要です。これ、突っ込んで考えるとかなりまぎわらしいですね。
しかし一般に「マネージリソース」というと、リソースの解放を開発者が全く考慮する必要がないオブジェクトというイメージがあり、
(「一般」の範囲がどこまで適用されるのかという問題もありますが、少なくとも私はこういう認識でおります)
Stream・Bitmap 等、アンマネージリソースを扱うクラスは、「マネージリソース」と呼ぶべきではないんじゃないかと思ってます。ネットで色々調べてみますと「マネージリソース」という言葉の使い方に対して、どうも境界線が曖昧なものを感じておりますので、ちょっとしばらく潜って調べてきたいと思います。
# 「カプセル化と隠蔽」の話を思い出しました。
# http://d.hatena.ne.jp/bleis-tift/20090201/1233426011
ひらぽん http://d.hatena.ne.jp/hilapon/ -
(1) については私の考えは異なります。
前述の通り、「どこからも参照されなくなった時点で」という前提を満たすために Dispose を実装した方がよい場合もあると考えるためです。
極端な具体例としては、1Gの配列を保持するクラスを、ユーザーの null 代入にゆだねるのはメモリリソースの消費量からみた場合に問題があると感じます(ローカルでしか使われないという想定はもちろんしていません)。
大抵の場合、IDisposableを実装する形の方がむしろ解放は遅れる傾向になるでしょう。
大抵の場合、Disposeなしで自然に参照が解放されるタイミングは、Dispose呼び出しを使う場合よりも早くなります。
ローカルなオブジェクトなら当然そうなりますし、別のクラスがメンバとして使用するような場合でも、一般に保持する側のDisposeのタイミングに合わされるだけですから、結局似たような状況になります。IDisposableを実装した場合、利用する側は普通確実にDisposeを呼び出すように使いますので、Dispose呼び出しが実行されるまで解放が遅れる(参照が維持される)結果になります。
もし大きなリソースを使うことが分かっているようなクラスで、場合によっては明示的に開放した方がよい場合(リソースを必要とする期間よりも、オブジェクトの参照を肘する期間が長くなる可能性があって危険な場合など)は、
IDisposeとかではなくて、別途解放用のメソッドを用意する方がよさそうに思います。 -
なちゃさんにいただいた返信で考えが変わりました。
(以下の前半は重箱の隅をつつくような話ですみません。もし的外れでしたら返信ください。)
> 一般に保持する側のDisposeのタイミングに合わされるだけですから、結局似たような状況になります。
「大抵の場合」として書かれたことは、ローカルに限定された話だと思いました(それと弱参照)。
また、親自体が何かのメンバになっていてそれに null が代入されない場合に私は着目していましたので、「似たような状況」というのも、似ている先の前提が少し違うように思いました。
> Disposeとかではなくて、別途解放用のメソッドを用意する方がよさそうに思います。
(引用から I をとりました)
明示的に解放した方がよい場合に限っては、それが一般的なんだと思えてきました。
(それ以外は親と一蓮托生のために放置)
私が論点にしているものに該当するクラスって Framework の中には無さそうですよね。。
そうすると、別メソッドになっているか放置されているかのどちらかになります。
たとえば List<T> には Dispose はありませんが、Clear というリスト操作に対する直感的なメソッドが参照の解除を兼ねていて、List においてはこれが一番適切なメソッド名だと感じます。
それに通常、Dispose 後のオブジェクトは再利用ができない点も、さらに Dispose の採用を制限するので、別途用意する方が柔軟性があって良さそうに思いました。
それに、一般的な認識外で Dispose を実装してしまうと、本当に Dispose されなければならない型が目立たなくなりそうな点も問題に思いました(Dispose 呼び出しの重要度に違いが生じてしまいますし)。
自分を納得させるために理由をいくつか考えましたが、私は一般的な解釈であることが一番だと思っていますので、考えを改めなければならないと思いました。
MicroVAX さんがまとめられた内容に大筋で同意します(と私が言っても価値ないですけど(^^;)。
話を長引かせてしまい失礼しました。 -
ネットで色々調べてみますと「マネージリソース」という言葉の使い方に対して、どうも境界線が曖昧なものを感じておりますので、ちょっとしばらく潜って調べてきたいと思います。
すこし潜って調べてきました。私の結論としては、リソースという用語は例えば次のように使うべきと思われる。
double[],string等 → マネージドリソース
Stream,Bitmap等 → アンマネージドリソース
(外部の)ポインター等 → アンマネージドリソース
になります。詳しくはこちらをご覧ください。http://d.hatena.ne.jp/hilapon/20100904/1283570083
ひらぽん http://d.hatena.ne.jp/hilapon/ -
Stream,Bitmap等 → アンマネージドリソース
http://d.hatena.ne.jp/hilapon/20100904/1283570083
ひらぽんさんのブログを読みました。
その解説に基づけばStreamクラスやBitmapクラスは明らかにマネージリソースでしょう。
内部にアンマネージリソースを持っていようが、そのクラス自身はマネージリソースです。
より厳密に区別するなら、アンマネージリソース、アンマネージリソースを内包するマネージリソース、(アンマネージリソースを内包しない)マネージリソースの3つに分類しておく方が誤解がないでしょうけど。
#後、Microsoftの用語の使い方としてはアンマネージやマネージのようにいい”ド”は付けません。 -
> #後、Microsoftの用語の使い方としてはアンマネージやマネージのようにいい”ド”は付けません。
ご指摘ありがとうございます。
> ひらぽんさんのブログを読みました。
> その解説に基づけばStreamクラスやBitmapクラスは明らかにマネージリソースでしょう。ただし、これはどうも納得できません。
マネージリソースの解説を幾つかあげると- マネージ リソースであれば、何もコードを追加しなくても、.NET Framework のガベージ コレクター (GC) が破棄してくれます。マネージ リソースに Using ブロックは必要ありません。
- 配列など
- オブジェクトが使用しているメモリ、それ以外の何物でもない
- マネージドリソース(GC ヒープ)
- 純粋に CLR の中だけで完結するリソース
またアンマネージの解説を見ると
- システムで共有され、マネージヒープの外部にあるリソース。
- CLR は直接それらのリソースを回収できない。
- 明示的に解放するようなプログラミングが求められる。
- OS リソースをラップしたリソース(ファイルハンドルやウィンドウハンドルなどをラップしたオブジェクトなど)、ユーザーが明示的に解放する必要があるもの
と言ってます。となれば、アンマネージリソースをラップし、Dispose パターンを持ちユーザーが明示的に解放する必要があるマネージオブジェクトも「アンマネージリソース」と呼んで何の差支えもないかと思いますが、いかがでしょうか。
とはいえ「アンマネージリソース」が開発者から完全に隠蔽され、開発者が Using や Dispose を全く実装する必要がないオブジェクトまで「アンマネージリソース」と呼ぶつもりはありませんが。
ひらぽん http://d.hatena.ne.jp/hilapon/ -
> マネージ リソースに Using ブロックは必要ありません。
これ、「Using ステートメント(Visual Basic)」の解説の1文ですね。
明白で単純に感じることを、ひらぽんさんはなぜ「突っ込んで考えるとかなりまぎわらしい」とおっしゃるのか実は意味がわからなかったのですが、この Using の説明を読むと確かに私もそう感じました。
ただ、この文章は誤解を与えかねず、問題があると思いました。
「アンマネージリソース」に対比する表現として、それ以外をオブジェクトを含めて「マネージリソース」と表現するのは一般的かと思います。実際にはオブジェクトであってもです。
それに対して上記引用文は、あくまで純粋にリソースを指しているんだろうと思いますが、リソースそのものに対して Using を使うことはマネージかどうかにかかわらず不可能ですから、「マネージオブジェクトに Using ブロックは必要ありません」と読めてしまい、その場合は正しくなくなります。
この文の必要性には疑問を感じました。
ひらぽんさんが挙げられたその他の項目については、特に納得されない理由にはならないと思いました。
> となれば、アンマネージリソースをラップし、Dispose パターンを持ちユーザーが明示的に解放する必要があるマネージオブジェクトも「アンマネージリソース」と呼んで何の差支えもないかと思いますが、いかがでしょうか。
IDisposable パターンの実装においては、それが文字通りリソースそのものを指す場合と、ひらぽんさんのようにオブジェクトを指す場合とでは、全く逆の扱いになってしまいますので、問題があると思います。
管理されているかされていないかが重要ですので、そこが逆転した表現は良くないと思います。
以下は IDisposable.Dispose メソッドのサンプルの抜粋ですが、
Bitmap.Dispose を行うのは「Dispose managed resources.」の箇所になります。
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;
} -
返信ありがとうございます。実はこのコードも読みました。
このサンプルは、マネージオブジェクトを Dispose するブロックの直前で「If disposing equals true, dispose all managed and unmanaged resources.」
とコメントしてます。
英文は弱いのですが、「disposing が true なら、すべてのマネージおよびアンマネージリソースを Dispose する」 と読めます。純粋なアンマネージリソースなら Dispose を実装してる筈がないので、ここも TH01 さんの仰るとおりなら、誤解を招きやすいところだと思います。// If disposing equals true, dispose all managed // and unmanaged resources. if(disposing) { // Dispose managed resources. component.Dispose(); }
またマネージリソースを一番理解していると思われる MSDN のドキュメントでも先に挙げた> マネージ リソースに Using ブロックは必要ありません。
http://msdn.microsoft.com/ja-jp/library/htd05whh.aspx
さらに
> マネージ リソース (配列など)
http://msdn.microsoft.com/ja-jp/library/fs2xkftw.aspx
と記述があるので、これも誤解を招きやすい表現だと思いました。
また Microsoft の荒井さんの著書(公式本ではありませんが) でも> アンマネージ = OS リソースをラップしたリソース(ファイルハンドルやウィンドウハンドルなどをラップしたオブジェクトなど)
とあります。この 「ラップしたオブジェクト」 という意味は、アンマネージリソースをラップしたマネージオブジェクトという意味にも読めるのですが・・・
もっとも私自身 Delphi→ VC++ → .NET と移ってきてまだ数年と日が浅いため、従来からの呼び方を知りません。「マネージオブジェクト」を総じて「マネージリソース」と呼ぶ文化(?)が定着しているのなら、それはそれで納得するところです。
ひらぽん http://d.hatena.ne.jp/hilapon/ -
> 純粋なアンマネージリソースなら Dispose を実装してる筈がないので、ここも TH01 さんの仰るとおりなら、誤解を招きやすいところだと思います。
誤解する人もいるとは思いますが、
if (disposing) { managed } else { unmanaged }
となっているわけではありませんので、間違いではないですね。
(それと念のためですが、動詞の dispose は Dispose を呼び出すという意味でもないです。)
邪推すると、このコメントはあえてそのような表現になっていると、私は思いました。
ユーザーの Dispose メソッドの呼び出しに伴う Dispose(bool disposing) の呼び出しでは、
(= disposing が true の場合は、)
マネージだけではなくアンマネージも解放する、
ということを示すためじゃないかと考えます。
(追記:それと、その下の方の false の場合のコメントと対比した内容になっています。)
> マネージ リソース (配列など)
私は配列等はマネージリソースと考えています。
今回のスレッドで、Dispose 内での解放(null 代入)は不要(しても無意味)と認識できましたけど。
> アンマネージ = OS リソースをラップしたリソース(ファイルハンドルやウィンドウハンドルなどをラップしたオブジェクトなど)
こちらはよくわかりませんでした。
ただ、あくまで CLR 管理下かどうかがポイントで、オブジェクトという表現としては、
「IDisposable を実装したオブジェクト」⊂「.NET のオブジェクト」⊂「オブジェクト」
という関係があると思います。(COM オブジェクトが右端など)- 編集済み TH01 2010年9月7日 12:14 気づいたことを追記
-
返信ありがとうございます。TH01 さんには、ここまでお付き合いいただき感謝しております。言葉は一つでも、突っ込んで考えるとここまで深いのかと改めて考えさせられましたが、大変勉強になりました。
ブログを通して The Root of .NET Framework の著者の荒井さんから話を伺いました。それによると、厳密にはこうなりそうです。
対象となるオブジェクト リソースの種類 double[],string等 マネージリソース Stream,Bitmap等 マネージリソース ポインター等 マネージリソース ポインターが指す外部リソース アンマネージリソース 「CLRの世界で考えれば、マネージヒープに存在するものは全てがGCの対象です。この中にネイティブポインターがあったとしてもです。何故なら、ネイティブポインタそのものは 32 Bit の整数でしかない」 とのことです。
あと 「リソースの言葉の定義に問題がある」 として、私のブログにコメントいただきました。どうやら 「リソース」 という言葉の混同(?)が混乱を招いていたようです。
ひらぽん http://d.hatena.ne.jp/hilapon/ -
Microsoft の荒井さんが本スレッドの議論を読まれて CLRから見たリソースについて という記事を起こして下さいました。
http://blogs.msdn.com/b/shozoa/archive/2010/09/08/about-resources-on-clr.aspx
私の疑問 「アンマネージリソースをラップしたオブジェクトはやはりアンマネージリソースではないか」 は、誤った認識だということで解決させて頂きましたが、荒井さんにはお忙しい中わざわざ愚問にお付き合い頂き、記事にまで起こしてもらいました。本当に有難い限りです。
ひらぽん http://d.hatena.ne.jp/hilapon/ -
こちらでコメントさせていただきます。
[ひらぽんさんのブログへの荒井さんのコメントより引用]
> ですから、フォーラムの議論のように大量の配列を回収させるためにIDiposableを実装するということも必要になれば行っても良いということになります。
私としてはこれだけで荒井さんの大ファンになりました!
もちろん必要であれば、ですし、それに加えて別メソッドの検討も重要だと今回思いました。
LOH の話の P108 って気になるので、私も本買ってきますね!!!
[同ブログのひらぽんさんのコメント]
> 個人的にも DataTable 等は大量のヒープを消費するため、マネージリソースといえども明示的に Dispose するよう日頃から実装してます。
DataTable は MarshalByValueComponent から継承しているために Dispose を持っていますが、DataTable としては何も実装していなかったりします。
なので、使用場面によっては最低限 Clear さえしておけば呼ぶ必要は無いとずっと考えていました(実際はしてますけど)。
ただ、最近のスレッドなどから解放タイミングを厳密に把握することができましたので、これも考えを改めました。
[荒井さんのブログより]
> MSDNフォーラムで盛り上がっているというのを
私が騒いでいたところに、ひらぽんさんが参戦という感じでしょうか。。。
(変な言い方でひらぽんさんごめんなさい。荒井さんのご意見がお聞きできたのはひらぽんさんのおかげなのに(^^;) -
DataTable は MarshalByValueComponent から継承しているために Dispose を持っていますが、DataTable としては何も実装していなかったりします。
え、そうなのですかっ?!それは初耳ですが、それってソースあります??(@@;)
ひらぽん http://d.hatena.ne.jp/hilapon/
DataTable.Dispose
http://msdn.microsoft.com/ja-jp/library/system.data.datatable.dispose.aspx
文字通りのソースコードもありますが、DataTableはDisposeメソッドをオーバーライドしていません。
MarshalByValueComponentクラスのDisposeメソッドは実装があり、Siteプロパティ、Eventsプロパティが使用されている場合にそれらの後始末をしているようです。
#完全に空の実装というわけではないですし、
#上記の詳細の話はあくまで実装レベルでの話なので、
#Disposeメソッドを呼んでおくことに越したことはないと思います。- 回答としてマーク MicroVAX 2014年11月28日 4:31