c# 警告 IDisposableインターフェイスの適切な使用




idisposable dispose (16)

私はIDisposableインターフェイスの "プライマリ"の使用が管理されていないリソースをクリーンアップすることをMSDNのドキュメントから知っています。

私には、 "管理対象外"とはデータベース接続、ソケット、ウィンドウハンドルなどを意味しますが、 Dispose()メソッドが実装されている管理対象リソースを解放するコードを見たことがあります。あなたのためにそれを世話してください。

例えば:

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で通常よりも速く使用することですか?

編集 :これまでのところ、IDisposableを使用してデータベース接続やビットマップなどの管理されていないリソースをクリーンアップする良い例がいくつか掲載されています。 しかし、上記のコードの_theListには100万個の文字列が含まれていて、ガベージコレクタを待つのではなく、そのメモリを解放したいとします。 上記のコードはそれを達成するでしょうか?


IDisposable イベントから退会するのに適しています。


ほとんどの「管理されていないリソース」についての議論の1つの問題は、それらが実際に用語を定義していないことですが、アンマネージドコードと関係があることを暗示しているようです。多くの種類のアンマネージドリソースがアンマネージコードとのインターフェイスを取ることは真実ですが、アンマネージドリソースをこのような観点から考えることは役に立ちません。

代わりに、すべての管理されたリソースが共通していることを認識する必要があります。つまり、外部の「もの」に何かを実行して他の「もの」を犠牲にすることを要求するオブジェクトと、そうすることに同意する他のエンティティ追っての通知。オブジェクトを放棄してトレースなしで消滅させる場合、存在しなくなったオブジェクトの代わりにその動作を変更する必要がなくなったことを外部のものに伝えるものはありません。その結果、 '物の有用性は永久に減少するだろう。

したがって、管理対象外のリソースは、オブジェクトの代わりにその動作を変更するために、外部の「もの」による合意を表します。オブジェクトが放棄され、存在しなくなった場合、外部の「もの」の有用性を損なうことはありません。管理対象リソースは、そのような契約の受益者であるが、放棄された場合に通知を受け取るように署名し、破棄される前にその通知を順番に使用するオブジェクトです。


あなたの与えられたコードサンプルは、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.
    }
}

Disposeパターンの目的は、管理されたリソースと管理されていないリソースの両方をクリーンアップするメカニズムを提供することです。このとき、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);
    }
}

ここで最も重要な方法は、実際には2つの異なる状況下で実行されるDispose(bool)です。

  • disposing == true:メソッドは、直接的または間接的にユーザーのコードによって呼び出されました。 管理リソースと非管理リソースを廃棄できます。
  • disposing == false:メソッドはファイナライザ内部からランタイムによって呼び出されており、他のオブジェクトは参照しないでください。 管理されていないリソースのみを廃棄できます。

GCが単純にクリーンアップを行うことが問題になるのは、GCが収集サイクル(GC.Collect()を呼び出すことができますが、本当にすべきではありません)をいつ実行するかについて実際の制御がないためです。必要以上に長くなります。 Dispose()を呼び出すと、実際にコレクションサイクルが発生したり、GCによってオブジェクトが収集/解放されたりすることはありません。 使用されたリソースをより決定論的にクリーンアップし、このクリーンアップがすでに実行されたことをGCに通知する手段を提供するだけです。

IDisposableとdisposeパターンの全体のポイントは、すぐにメモリを解放することではありません。 Disposeへの呼び出しで実際にメモリをただちに解放できるのは、disposing == falseシナリオを処理し、管理されていないリソースを操作している場合だけです。 マネージコードの場合、実際にGCがコレクションサイクルを実行するまでメモリは再利用されません。これは実際には制御できません(GC.Collect()を呼び出す以外にも、私はすでに言及しています)。

あなたのシナリオは本当に有効ではありません.NETの文字列はunamangedリソースを使用せず、IDisposableを実装していないので、強制的に "クリーンアップ"する方法はありません。


何かあれば、コードを放置するよりも効率が悪くなると思います。

Clear()メソッドを呼び出すことは不要で、Disposeがそれをしなかった場合、GCはおそらくそれをしません...


私は、管理されたリソースと管理されていないリソースの両方でIDisposableを使用することについて、多くの回答が話題に移っていることがわかります。私はIDisposableが実際にどのように使われるべきかについて私が見いだした最良の説明の1つとして、この記事を提案します。

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

実際の質問については、あなたはIDisposableを使用して多くのメモリを占有している管理対象オブジェクトをクリーンアップしなければなりません。その理由は、IDisposableをDisposeすると、そのIDisposableを範囲外にする必要があるからです。その時点で、参照されている子オブジェクトも範囲外であり、収集されます。

実際の例外は、管理されたオブジェクトにたくさんのメモリを抱えていて、そのスレッドが処理を待つのをブロックした場合です。呼び出しが完了した後でそれらのオブジェクトが必要ない場合は、これらの参照をnullに設定すると、ガーベジコレクターはそれらをより早く収集することができます。しかし、このシナリオは、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
}

Disposeが呼び出された後では、オブジェクトのメソッドへの呼び出しはもう必要ありません(オブジェクトはそれ以降のDisposeの呼び出しを許容する必要があります)。 したがって、その問題の例は愚かです。 Disposeが呼び出された場合、オブジェクト自体は破棄されます。 そのため、ユーザーはそのオブジェクト全体への参照をすべて破棄して(nullに設定する)、内部のすべての関連オブジェクトが自動的にクリーンアップされます。

マネージド/アンマネージドに関する一般的な質問と他の回答の議論については、この質問に対する回答は、アンマネージドリソースの定義から始める必要があると思います。

それは、システムを状態にするために呼び出すことができる関数があり、それをその状態から戻すために呼び出すことのできる別の関数があることです。 さて、典型的な例では、最初のものはファイルハンドルを返す関数であり、2番目のものはCloseHandleへの呼び出しです。

しかし、これが鍵です - それはどんな機能の一致するペアでもあります。 1つは状態を構築し、もう1つはそれを裂く。 状態が構築されているが、まだ切断されていない場合は、そのリソースのインスタンスが存在します。 適切なタイミングでティアダウンが行われるように手配しなければなりません。リソースはCLRによって管理されません。 自動的に管理されるリソースタイプはメモリのみです。 GCとスタックの2種類があります。 値の型はスタックによって管理されます(または参照型の内部に乗ることによって)、参照型はGCによって管理されます。

これらの関数は、自由にインターリーブすることができる状態変化を引き起こしたり、完全に入れ子にする必要があります。 状態の変化はスレッドセーフであるか、そうでない可能性があります。

正義の疑問の例を見てください。 ログファイルのインデントの変更は完全に入れ子になっていなければなりません。 また、スレッドセーフではありません。

ガベージコレクタを使用して乗車をして、管理されていないリソースをクリーンアップすることは可能です。 しかし、状態変更関数がスレッドセーフであり、2つの状態が重複する生存期間を持つことができる場合に限ります。 だからJusticeのリソースの例はファイナライザを持っていてはいけません! それは誰にも役立たないだろう。

これらのリソースの場合、ファイナライザなしでIDisposable実装するだけで済みます。 ファイナライザは絶対にオプションです。 これは、多くの書籍で言及されていない、または言及されていない。

次に、 usingステートメントを使用しusingDisposeが確実に呼び出されるようにする必要があります。 これは本質的にスタックとの乗り心地に似ています(ファイナライザーは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を使用する必要があります(ただし、スレッドセーフを実装していない限り、ファイナライザはまだありませんが、これは無料ではありません)。


とにかくMyCollectionがガベージコレクションされる場合は、それを処分する必要はありません。 そうすることで、必要以上にCPUが乱れてしまい、ガベージコレクタがすでに実行していた事前計算済みの分析が無効になることさえあります。

私はIDisposableを使用して、スレッドが正しく配置されているかどうか、管理されていないリソースなどを確認します。

編集スコットのコメントに応じて:

GCパフォーマンスメトリクスが影響を受ける唯一の時間は、[sic] GC.Collect()コールが "

概念的には、GCはオブジェクト参照グラフのビューと、スレッドのスタックフレームからの参照を保持します。 このヒープは非常に大きく、メモリの多くのページにまたがる可能性があります。 最適化として、GCは頻繁に変更される可能性が低いページの分析をキャッシュして、ページを不必要に再スキャンしないようにします。 GCは、ページ内のデータが変更されたときにカーネルから通知を受け取り、ページがダーティで再スキャンが必要であることを認識します。 コレクションがGen0にある場合、ページ内の他のものも変更されている可能性がありますが、これはGen1とGen2ではあまりありません。 ちょっとしたことに、これらのフックは、そのプラットフォームでSilverlightプラグインを動作させるためにGCをMacに移植したチームのMac OS Xでは利用できませんでした。

リソースの不必要な廃棄に対するもう一つのポイント:プロセスがアンロードされている状況を想像してください。 プロセスがしばらく実行されていることも想像してください。 そのプロセスのメモリページの多くがディスクにスワップされている可能性があります。 少なくとも彼らはもはやL1またはL2キャッシュにいません。 このような状況では、プロセスが終了したときにオペレーティングシステムによって解放されるリソースを解放するために、それらのデータおよびコードページをすべてメモリに戻すためにアンロードするアプリケーションのポイントはありません。 これは、管理対象リソースおよび特定の管理対象外リソースに適用されます。 バックグラウンドでないスレッドを維持するリソースだけを廃棄しなければなりません。さもなければ、プロセスは生き続けるでしょう。

現在のところ、通常の実行中に、管理対象外のメモリリークを避けるために (@fezmonkeyがデータベース接続、ソケット、ウィンドウハンドルを指摘する)一時的なリソースが正しくクリーンアップされなければなりません。 これらは処分されなければならない種類のものです。 スレッドを所有しているクラスを作成している場合(そして、自分が所有しているので、作成したもので、少なくともコード化スタイルで停止することになります)、そのクラスはIDisposableを実装し、 Dispose

.NET Frameworkは、このクラスを廃棄する必要があることを示す警告として、 IDisposableインターフェイスをシグナルとして使用します。 私は、処分がオプションであるIDisposable (明示的なインターフェイスの実装を除く)を実装するフレームワーク内のどの型も考えることはできません。


別に制御する方法として、その主な用途から生涯システムリソースを(完全に素晴らしい答えでカバーイアン、栄光を!)、IDisposableを/使用してコンボすることも使用することができますスコープ(クリティカル)グローバルリソースの状態変化コンソールスレッドプロセス、任意のグローバル・オブジェクトのようなアプリケーションインスタンス

私はこのパターンに関する記事を書いた:http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/ : http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

コンソールカラー、現在のスレッドカルチャExcelアプリケーションオブジェクトのプロパティなど、再利用可能読みやすい方法で頻繁に使用されるグローバル状態を保護する方法を示しています。


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.");
        }
    }
}

最初の定義。私にとっては、管理されていないリソースとは、IDisposableインターフェイスやdllの呼び出しを使って作成された何らかのクラスを意味します。GCはそのようなオブジェクトの処理方法を知らない。クラスに値の型だけがある場合は、このクラスを管理されていないリソースのクラスとはみなしません。私のコードでは、私は次の慣行に従います:

  1. 私が作成したクラスでは、いくつかの管理されていないリソースが使用されると、メモリを消去するためにIDisposableインターフェイスも実装する必要があります。
  2. オブジェクトの使用が終了するとすぐにオブジェクトをクリーンアップします。
  3. 私の処理メソッドでは、クラスのすべてのIDisposableメンバーを繰り返し、Disposeを呼び出します。
  4. 私のDisposeメソッドでGC.SuppressFinalize(this)を呼び出して、ガベージコレクタにオブジェクトがすでにクリーンアップされたことを通知します。GCの呼び出しは高価な操作なので、私はそれを行います。
  5. 私は、Dispose()を複数回呼び出すことを可能にしようとしています。
  6. ときどきプライベートメンバ_disposedを追加し、オブジェクトがクリーンアップされたかどうかのメソッド呼び出しをチェックインします。それがクリーンアップされた場合は、ObjectDisposedException を生成します。
    次のテンプレートは、私が単語の中でコードのサンプルとして示したことを示しています。

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;
        }
    }


私は、管理されていないリソースを使用したり解放したりすることについて、通常のことを繰り返すことはありません。しかし、私はよくある誤解のようなものを指摘したいと思います。
与えられたコード

Public Class LargeStuff
  Implements IDisposable
  Private _Large as string()

  'Some strange code that means _Large now contains several million long strings.

  Public Sub Dispose() Implements IDisposable.Dispose
    _Large=Nothing
  End Sub

Disposable実装は現在のガイドラインに従わないが、皆さんがすべてのアイデアを得ることを願っています。
今、Disposeが呼び出されると、どれくらいのメモリが解放されますか?

答え:なし。
Disposeを呼び出すと、アンマネージリソースを解放することができます。マネージメモリを再利用することはできません.GCだけがそれを行うことができます。上記のパターンに従えば、上記が良い考えではないと言っていないことは、実際にはまだまだ良いアイデアです。 Disposeが実行されると、LargeStuffのインスタンスがまだスコープ内にあっても、GCが_Largeによって使用されていたメモリの再要求を停止することはありません。 _Largeの文字列もgen 0にある可能性がありますが、LargeStuffのインスタンスはgen 2である可能性があります。この場合も、メモリはすぐに再要求されます。
しかし、上に示したDisposeメソッドを呼び出すファイナライザを追加する必要はありません。それはファイナライザを実行できるようにメモリの再要求を遅らせるだけです。


管理リソースを廃棄するための最も正当な使用事例は、他の方法では収集されないリソースを回収するGCの準備です。

代表的な例は循環参照です。

循環参照を避けるパターンを使用することがベストプラクティスですが、(例えば)「親」への参照を持つ「子」オブジェクトで終了すると、放棄したばかりの親のGC収集を停止することができます参照とGCに依存+プラスファイナライザを実装している場合、それは決して呼び出されません。

これを回避する唯一の方法は、循環参照を手動で破棄することです。

これを行うには、親と子にIDisposableを実装するのが最善の方法です。Disposeが親に対して呼び出され、すべての子に対してDisposeを呼び出し、子Disposeメソッドで親参照をnullに設定します。


このDispose()サンプルコードでは、オブジェクトの通常のGCのために発生しない効果がある可能性がある操作がありMyCollectionます。

オブジェクトが他のオブジェクトによって参照_theListまたは参照されている場合、そのオブジェクトまたはオブジェクトはコレクションの対象にはなりませんが、突然コンテンツはありません。この例のようにDispose()操作がなかった場合でも、それらのコレクションには内容が残っています。_theDictList<>Dictionary<>

これは私が壊れたデザイン、それを呼ぶような状況だった場合はもちろん、 -私はちょうどことを(私が思う、pedantically)指摘していますDispose()操作は、他の用途があるかどうかに応じて、完全に冗長ではないかもしれませんList<>か、Dictionary<>ではありませんその断片に示されている。





idisposable