IDisposable
インターフェースの「主な」用途は、管理されていないリソースをクリーンアップすることであることを私は読んでいます マイクロソフトのドキュメント 。
私にとって、「管理されていない」とは、データベース接続、ソケット、ウィンドウハンドルなどのことを意味します。しかし、Dispose()
メソッドがmanagedリソースを解放するために実装されているコードを見ました。ガベージコレクターはあなたのためにそれを大事にするべきです。
例えば:
public class MyCollection : IDisposable
{
private List<String> _theList = new List<String>();
private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();
// Die, clear it up! (free unmanaged resources)
public void Dispose()
{
_theList.clear();
_theDict.clear();
_theList = null;
_theDict = null;
}
私の質問は、これにより、ガベージコレクタがMyCollection
で使用されていたメモリを通常よりも速くすることができるかどうかということです。
edit :これまでのところ、データベース接続やビットマップなどの管理されていないリソースをクリーンアップするためにIDisposableを使用するいくつかの良い例が投稿されています。しかし、上記のコードの_theList
に100万の文字列が含まれていて、ガベージコレクタを待つのではなく、そのメモリnowを解放したいとします。上記のコードはそれを達成しますか?
IDisposable
は、using
ステートメントを悪用し、管理対象オブジェクトを確定的にクリーンアップする簡単な方法を利用するためによく使用されます。
public class LoggingContext : IDisposable {
public Finicky(string name) {
Log.Write("Entering Log Context {0}", name);
Log.Indent();
}
public void Dispose() {
Log.Outdent();
}
public static void Main() {
Log.Write("Some initial stuff.");
try {
using(new LoggingContext()) {
Log.Write("Some stuff inside the context.");
throw new Exception();
}
} catch {
Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
} finally {
Log.Write("Some final stuff.");
}
}
}
Disposeパターンの目的は、管理対象リソースと管理対象外リソースの両方をクリーンアップするメカニズムを提供することであり、それがいつ発生するかは、Disposeメソッドの呼び出し方法によって異なります。あなたの例では、リストをクリアしてもそのコレクションが破棄されることに影響を与えないので、Disposeの使用は実際には破棄に関連することは何もしていません。同様に、変数をnullに設定するための呼び出しもGCに影響を与えません。
Disposeパターンの実装方法の詳細については、この article をご覧ください。基本的には次のようになります。
public class SimpleCleanup : IDisposable
{
// some fields that require cleanup
private SafeHandle handle;
private bool disposed = false; // to detect redundant calls
public SimpleCleanup()
{
this.handle = /*...*/;
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Dispose managed resources.
if (handle != null)
{
handle.Dispose();
}
}
// Dispose unmanaged managed resources.
disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
ここで最も重要なメソッドはDispose(bool)です。これは実際には2つの異なる状況で実行されます。
単純にGCにクリーンアップを実行させることの問題は、GCがいつ収集サイクルを実行するかを実際に制御できないことです(GC.Collect()を呼び出すことはできますが、実際には行わないでください)。必要以上に長い。覚えておいて、Dispose()を呼び出しても、実際には収集サイクルが発生したり、GCがオブジェクトを収集/解放したりすることはありません。使用されているリソースをより決定的にクリーンアップし、このクリーンアップがすでに実行されたことをGCに伝える手段を提供するだけです。
IDisposableと破棄パターンのすべてのポイントは、すぐにメモリを解放することではありません。 Disposeへの呼び出しが実際にメモリを即座に解放する機会を実際に得ることができる唯一の時間は、破棄する== falseシナリオを処理し、管理されていないリソースを操作するときです。マネージコードの場合、GCがコレクションサイクルを実行するまでメモリは実際には再利用されません。これは、制御できません(既に説明したGC.Collect()を呼び出す以外はお勧めできません)。
.NETの文字列は何も変わっていないリソースを使用せず、IDisposableを実装していないので、あなたのシナリオは実際には有効ではありません。
Disposeが呼び出された後、オブジェクトのメソッドをそれ以上呼び出すことはできません(ただし、オブジェクトはそれ以降のDisposeの呼び出しを許容するはずです)。したがって、問題の例はばかげています。 Disposeが呼び出された場合は、オブジェクト自体を破棄できます。そのため、ユーザーはそのオブジェクト全体への参照をすべて破棄し(nullに設定する)、その内部のすべての関連オブジェクトは自動的にクリーンアップされます。
管理された/管理されていないことについての一般的な質問と他の答えでの議論に関しては、私はこの質問への答えが管理されていないリソースの定義から始まるべきだと思います。
結局のところ、システムをある状態にするために呼び出すことができる関数があり、それをその状態から戻すために呼び出すことができる別の関数があります。さて、典型的な例では、最初のものはファイルハンドルを返す関数で、2番目のものはCloseHandle
の呼び出しです。
しかし - そしてこれが鍵となります - それらはどんなマッチする関数のペアでもあり得ます。 1つは状態を作り上げ、もう1つはそれを壊します。状態が構築されているがまだ破棄されていない場合は、リソースのインスタンスが存在します。適切なタイミングでティアダウンが発生するように手配する必要があります。リソースはCLRによって管理されていません。自動的に管理される唯一のリソースタイプはメモリです。 GCとスタックの2種類があります。値型はスタックによって(または参照型の内側に乗ることによって)管理され、参照型はGCによって管理されます。
これらの関数は、自由にインターリーブできる状態の変化を引き起こす可能性があります、または完全にネストする必要があるかもしれません。状態の変更はスレッドセーフである場合もあれば、そうでない場合もあります。
正義の質問の例を見てください。ログファイルのインデントへの変更は完全にネストされていなければなりません、さもなければそれはすべてうまくいきません。また、スレッドセーフになる可能性は低いです。
管理されていないリソースをクリーンアップするために、ガベージコレクタに乗ることができます。ただし、状態変更機能がスレッドセーフであり、2つの状態のライフタイムが重複している場合に限ります。だから正義のリソースの例はファイナライザーを持ってはいけません!誰にも役に立たないでしょう。
そのような種類のリソースには、ファイナライザーなしでIDisposable
を実装することができます。ファイナライザは絶対にオプションです - それはする必要があります。これは多くの本で説明されているか言及されていません。
using
が確実に呼び出されるようにするには、Dispose
ステートメントを使用する必要があります。これは基本的にスタックに乗るのをやめるようなものです(つまりファイナライザはGCに、using
はスタックになります)。
欠けている部分は手動でDisposeを書いてそれをあなたのフィールドとあなたの基本クラスに呼び出させる必要があるということです。 C++/CLIプログラマーはそうする必要はありません。ほとんどの場合、コンパイラはそれを書き込みます。
完全に入れ子になっていてスレッドセーフではない状態には別の方法があります(IDisposableを避けること以外は、IDisposableを実装するすべてのクラスにファイナライザーを追加することに抵抗することはできません。
クラスを書く代わりに、関数を書きます。この関数は、次のものにコールバックするためのデリゲートを受け入れます。
public static void Indented(this Log log, Action action)
{
log.Indent();
try
{
action();
}
finally
{
log.Outdent();
}
}
そして簡単な例は次のようになります。
Log.Write("Message at the top");
Log.Indented(() =>
{
Log.Write("And this is indented");
Log.Indented(() =>
{
Log.Write("This is even more indented");
});
});
Log.Write("Back at the outermost level again");
渡されるラムダはコードブロックとして機能するので、呼び出し側がそれを悪用する危険性がなくなることを除いて、using
と同じ目的を果たすように独自の制御構造を作成するのと同じです。彼らがリソースをクリーンアップするのに失敗することができる方法はありません。
リソースがライフタイムが重複する可能性がある種類のリソースの場合、この手法はあまり役に立ちません。リソースAを作成し、次にリソースBを作成してからリソースAを終了し、後でリソースBを終了することができるからです。ユーザーがこのように完全にネストするように強制した場合しかし、その場合はIDisposable
を使用する必要があります(ただし、スレッドセーフを実装していない限り、ファイナライザは使用できません)。
IDisposableを利用するシナリオ:管理されていないリソースのクリーンアップ、イベントの登録解除、接続の切断
私がIDisposableを実装するために使用する慣用句( スレッドセーフではない ):
class MyClass : IDisposable {
// ...
#region IDisposable Members and Helpers
private bool disposed = false;
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing) {
if (!this.disposed) {
if (disposing) {
// cleanup code goes here
}
disposed = true;
}
}
~MyClass() {
Dispose(false);
}
#endregion
}
うん、そのコードは完全に冗長で不必要であり、そうでなければ(MyCollectionのインスタンスが範囲外になったら、つまり)、ガベージコレクタに何もさせません。特に.Clear()
は呼び出します。
あなたの編集に回答します。私がこれをするならば:
public void WasteMemory()
{
var instance = new MyCollection(); // this one has no Dispose() method
instance.FillItWithAMillionStrings();
}
// 1 million strings are in memory, but marked for reclamation by the GC
メモリ管理の目的では、機能的にこれと同じです。
public void WasteMemory()
{
var instance = new MyCollection(); // this one has your Dispose()
instance.FillItWithAMillionStrings();
instance.Dispose();
}
// 1 million strings are in memory, but marked for reclamation by the GC
この瞬間にメモリを本当に本当に解放する必要があるなら、GC.Collect()
を呼び出してください。ただし、これを行う理由はありません。メモリは必要なときに解放されます。
とにかくMyCollection
がガベージコレクションされる場合は、廃棄する必要はありません。これを行うと、CPUが必要以上にチャーンし、ガベージコレクターが既に実行した事前に計算された分析が無効になる場合があります。
IDisposable
を使用して、管理されていないリソースとともにスレッドが正しく破棄されるようにします。
EDIT Scottのコメントへの応答:
GCパフォーマンスメトリックが影響を受けるのは、[sic] GC.Collect()が呼び出されたときだけです」
概念的には、GCはオブジェクト参照グラフのビューと、スレッドのスタックフレームからのすべての参照を保持します。このヒープは非常に大きく、多くのメモリページにまたがることがあります。最適化として、GCはページを不必要に再スキャンしないように、頻繁に変更される可能性が低いページの分析をキャッシュします。 GCは、ページ内のデータが変更されたときにカーネルから通知を受け取るため、ページがダーティであり、再スキャンが必要であることを認識しています。コレクションがGen0にある場合、ページ内の他の要素も変化している可能性がありますが、これはGen1およびGen2にはありません。逸話的に、これらのフックは、GCをMacに移植したチームがMac OS Xで使用できず、そのプラットフォームでSilverlightプラグインを動作させることができませんでした。
リソースの不必要な廃棄に対するもう1つのポイント:プロセスがアンロードされる状況を想像してください。プロセスがしばらく実行されていることも想像してください。そのプロセスのメモリページの多くがディスクにスワップされている可能性があります。少なくとも、L1またはL2キャッシュにはありません。このような状況では、プロセスが終了したときにオペレーティングシステムによって解放されるリソースを「解放」するために、すべてのデータとコードページをメモリにスワップしてアンロードするアプリケーションには意味がありません。これは、管理対象および特定の非管理対象リソースにも適用されます。バックグラウンド以外のスレッドを存続させるリソースのみを破棄する必要があります。そうしないと、プロセスは存続します。
現在、通常の実行中に、管理されないようにするために、(@ fezmonkeyがデータベース接続、ソケット、ウィンドウハンドルを指摘しているように)正しくクリーンアップする必要がある一時リソースがありますメモリリーク。これらは処分しなければならない種類のものです。スレッドを所有するクラスを作成する場合(それは、それを作成したため、少なくとも私のコーディングスタイルによって停止することを保証することを意味します)、おそらくそのクラスはIDisposable
とtearを実装する必要がありますDispose
の間にスレッドを停止します。
.NETフレームワークは、このクラスmustを破棄する必要があるという開発者への信号、さらには警告として、IDisposable
インターフェイスを使用します。廃棄がオプションであるIDisposable
(明示的なインターフェイス実装を除く)を実装するフレームワーク内の型は考えられません。
今すぐ削除したい場合 、 アンマネージドメモリ を使用してください。
見る:
あなたが投稿した例では、それはまだ「今メモリを解放する」というわけではありません。すべてのメモリはガベージコレクションされますが、メモリを以前の generation に収集することができます。念のためいくつかのテストを実行する必要があります。
フレームワーク設計ガイドラインはガイドラインであり、規則ではありません。彼らは、インターフェースが主に何のためにあるのか、いつ使うのか、どう使うのか、そしていつ使わないのかを教えてくれます。
私はかつてIDisposableを利用して失敗したときに単純なRollBack()であるコードを読みました。以下のMiniTxクラスはDispose()のフラグをチェックし、Commit
の呼び出しが一度も起こらなかった場合はそれ自身でRollback
を呼び出します。それは呼び出しコードを理解し、維持することをずっと容易にする間接層を追加しました。結果は次のようになりました。
using( MiniTx tx = new MiniTx() )
{
// code that might not work.
tx.Commit();
}
私はタイミング/ロギングコードも同じことをするのを見ました。この場合、Dispose()メソッドはタイマーを停止し、ブロックが終了したことをログに記録しました。
using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
// code to time...
}
管理されていないリソースのクリーンアップは行わないが、よりクリーンなコードを作成するためにIDisposableを使用することに成功した、2つの具体例がここにあります。
管理されていないリソースの使用または解放に関する通常のことは繰り返し説明しません。しかし、私は一般的な誤解のように思われるものを指摘したいと思います。
次のコードを考えます
Public Class LargeStuff IDisposable Private _Large as string() を実装する。 Public Sub Dispose()を実装しますIDisposable.Dispose _Large = Nothing End Sub
私は使い捨ての実装は現在のガイドラインに従わないことを認識していますが、うまくいけばあなた全員がアイデアを得ます。
さて、Disposeが呼ばれると、どれだけのメモリが解放されるのでしょうか。
答え:なし。
Disposeを呼び出すと、管理されていないリソースを解放できます。管理対象のメモリを再利用することはできません。GCだけがそれを実行できます。上記のパターンに従うことは、実際にはまだ良いアイデアであると言ってもいいのではありません。 Disposeが実行されると、LargeStuffのインスタンスがまだ有効範囲内にある場合でも、_Largeによって使用されていたメモリをGCが再要求するのを止めることはできません。 _Largeの文字列もgen 0になることがありますが、LargeStuffのインスタンスはgen 2になることがあるため、やはりメモリの再要求が早くなります。
上記のDisposeメソッドを呼び出すファイナライザを追加しても意味がありません。ファイナライザを実行できるようにするには、メモリの再要求を遅らせるだけです。
システムリソースのlifetimeを制御する方法としての主な使用とは別に(Ianの素晴らしい答えで完全にカバーされています! )、IDisposable/usingコンボを使用して(クリティカル)グローバルリソースの状態変化をスコープする:console、threads、process、application instanceのようなglobal object。
このパターンに関する記事を書きました: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/
reusableおよびreadableの方法で頻繁に使用されるグローバル状態を保護する方法を示しています:console colors、currentthread culture、Excelアプリケーションオブジェクトのプロパティ...
どちらかといえば、私はそれを除外したときよりも 少ない 効率的なコードになると思います。
Clear()メソッドを呼び出すことは不要であり、Disposeがそれを行わなかった場合、GCはおそらくそれをしないでしょう...
may のコード例では、Dispose()
操作がMyCollection
オブジェクトの通常のGCのために発生しない効果があるということがあります。
_theList
または_theDict
によって参照されているオブジェクトが他のオブジェクトによって参照されている場合、そのList<>
またはDictionary<>
オブジェクトはコレクションの対象にはなりませんが、突然内容がなくなります。例のようにDispose()操作がなかった場合でも、それらのコレクションにはまだ内容が含まれています。
もちろん、もしこれが壊れたデザインだと言うならば - List<>
またはDictionary<>
の他の用途があるかどうかに依存して、Dispose()
操作は完全に冗長ではないかもしれないことを指摘しているだけです。フラグメントには示されていません。
「管理されていないリソース」に関するほとんどの議論での1つの問題は、それらが実際に用語を定義していないということですが、管理されていないコードと関係があることを暗示しているようです。多くの種類の管理されていないリソースが管理されていないコードと連携するのは事実ですが、そのような用語で管理されていないリソースを考えることは役に立ちません。
そうではなく、すべての管理対象リソースに共通することを認識する必要があります。すべての管理対象リソースは、何か他の「もの」を損なうために外部の「もの」に依頼します。追っての通知。もしオブジェクトが痕跡なしに見捨てられそして消え去ることになっていたら、もはや存在していなかったオブジェクトに代わってその振る舞いを変更する必要がもはやないことをその外の「もの」に言うことはないでしょう。その結果、「ものの有用性は永久に減少します。
アンマネージリソースは、オブジェクトに代わってその動作を変更するという、何らかの外部の「もの」による合意を表します。これは、オブジェクトが破棄されて存在がなくなった場合、その外部の「もの」の有用性を損なうことになります。管理されたリソースはそのような協定の受益者であるが、それが放棄されれば通知を受け取るために申し込みをし、そしてそれが破壊される前にその問題を整理するためにそのような通知を使うでしょう。
管理されたリソースを処分するための最も正当な使用例は、そうでなければ決して収集されないであろうリソースを回収するためのGCの準備です。
主な例は循環参照です。
循環参照を回避するパターンを使用するのがベストプラクティスですが、(たとえば)その「親」に戻る参照を持つ「子」オブジェクトがある場合は、放棄すると親のGCコレクションが停止する可能性があります。参照とGCに頼る - そしてあなたがファイナライザを実装したならば、それは決して呼ばれないでしょう。
これを回避する唯一の方法は、子の親参照をnullに設定して循環参照を手動で分割することです。
これを行うには、親と子にIDisposableを実装するのが最善の方法です。親に対してDisposeが呼び出されたら、すべての子に対してDisposeを呼び出し、子のDisposeメソッドで、親参照をnullに設定します。
定義の最初の私にとって、アンマネージドリソースとは、IDisposableインターフェイスまたはdllの呼び出しを使用して作成されたものを実装するクラスを意味します。 GCはそのようなオブジェクトの扱い方を知りません。クラスが例えば値型のみを持つ場合、このクラスは管理されていないリソースを持つクラスとは見なされません。私のコードでは、次のプラクティスに従います。
public class SomeClass : IDisposable
{
/// <summary>
/// As usually I don't care was object disposed or not
/// </summary>
public void SomeMethod()
{
if (_disposed)
throw new ObjectDisposedException("SomeClass instance been disposed");
}
public void Dispose()
{
Dispose(true);
}
private bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)//we are in the first call
{
}
_disposed = true;
}
}
IDisposable
はイベントの購読を中止するのに適しています。
与えられたコードサンプルはIDisposable
の使用法の良い例ではありません。辞書の消去 通常 はDispose
メソッドに行ってはいけません。範囲外になると、辞書項目は消去されて破棄されます。範囲外になっても解放/解放されないメモリ/ハンドラを解放するには、IDisposable
実装が必要です。
次の例は、コードとコメントを含むIDisposableパターンの良い例です。
public class DisposeExample
{
// A base class that implements IDisposable.
// By implementing IDisposable, you are announcing that
// instances of this type allocate scarce resources.
public class MyResource: IDisposable
{
// Pointer to an external unmanaged resource.
private IntPtr handle;
// Other managed resource this class uses.
private Component component = new Component();
// Track whether Dispose has been called.
private bool disposed = false;
// The class constructor.
public MyResource(IntPtr handle)
{
this.handle = handle;
}
// Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
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.
protected virtual 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;
// Note disposing has been done.
disposed = true;
}
}
// Use interop to call the method necessary
// to clean up the unmanaged resource.
[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle);
// Use C# destructor syntax for finalization code.
// This destructor will run only if the Dispose method
// does not get called.
// It gives your base class the opportunity to finalize.
// Do not provide destructors in types derived from this class.
~MyResource()
{
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
Dispose(false);
}
}
public static void Main()
{
// Insert code here to create
// and use the MyResource object.
}
}
マネージドリソースとアンマネージドリソースの両方にIDisposableを使用することについて話すことに多くの答えが移っています。この記事は、IDisposableを実際に使用する方法について見つけた最良の説明の1つとしてお勧めします。
https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About
実際の質問について; IDisposableを使用して多くのメモリを使用している管理対象オブジェクトをクリーンアップすると、短い答えはnoになります。その理由は、いったんIDisposableを破棄したら、それをスコープの外に出すべきだからです。その時点で、参照される子オブジェクトもスコープ外になり、収集されます。
これに対する唯一の本当の例外は、管理対象オブジェクトに大量のメモリがあり、そのスレッドが何らかの操作の完了を待ってブロックしている場合です。その呼び出しが完了した後、それらのオブジェクトが必要にならない場合、それらの参照をnullに設定すると、ガベージコレクターがより早くそれらを収集できる場合があります。しかし、このシナリオは、リファクタリングが必要な不正なコードを表します-IDisposableのユースケースではありません。