1. ホーム
  2. c#

[解決済み] IDisposable インターフェースの正しい使用法

2022-03-16 08:16:18

質問内容

を読んで知っています。 マイクロソフトのドキュメント の主な用途は、「quot;primary" 」であることがわかりました。 IDisposable インターフェースは、管理されていないリソースをクリーンアップするためのものです。

私にとって、quot;unmanaged"とは、データベース接続、ソケット、ウィンドウハンドルなどのようなものを指します。 しかし、私が見たコードでは 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万個の文字列を含んでおり、そのメモリを解放したいとします。 ガベージコレクタを待つのではなく。 上記のコードで実現できるでしょうか?

解決方法は?

ディスポのポイント 管理されていないリソースを解放することです。これはある時点で実行される必要があり、そうでなければクリーンアップされることはありません。ガベージコレクタは どのように を呼び出すことができます。 DeleteHandle() 型の変数で IntPtr を知らないのであれば、それは かどうか を呼び出す必要があるかどうか。 DeleteHandle() .

備考 : とは何ですか? アンマネージドリソース ? Microsoft .NET Frameworkで見つけたのなら、それはマネージドです。もし、あなたが自分でMSDNをうろついたのなら、それはアンマネージドです。P/Invokeコールを使って、.NET Frameworkの快適な世界の外に出たものは、アンマネージドであり、その後始末はあなたの責任になります。

作成したオブジェクトには いくつか このメソッドは、管理されていないリソースをクリーンアップするために、外部から呼び出すことができます。このメソッドには好きな名前を付けることができます。

public void Cleanup()

または

public void Shutdown()

しかし、その代わりにこのメソッドには標準的な名称があります。

public void Dispose()

インターフェースも作られていた。 IDisposable そのメソッドだけを持つ

public interface IDisposable
{
   void Dispose()
}

そこで、オブジェクトに IDisposable インターフェイスを使用することで、アンマネージドリソースをクリーンアップするためのメソッドをひとつだけ書いたことを約束することができます。

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

で、終了です。 ただし、もっといいものができるはずだ。


オブジェクトが250MBを割り当てている場合はどうでしょう。 システム.ドローイング.ビットマップ (つまり、.NETで管理されたBitmapクラス)をある種のフレームバッファとして使用することはできますか? 確かに、これは管理された.NETオブジェクトであり、ガベージコレクタがそれを解放します。しかし、250MBのメモリをそのままにして、ガベージコレクタが解放するのを待ちますか? 結局 がやってきて、それを解放してくれるでしょうか?もし オープンデータベース接続 ? 確かに、GCがオブジェクトをファイナライズするのを待つために、その接続を開いたままにしておきたくはありません。

もしユーザーが Dispose() (つまり、もうこのオブジェクトを使う予定はない) 無駄なビットマップやデータベース接続を排除してはどうでしょう?

だから、今度はそうします。

  • 管理されていないリソースを削除する(必要だから)、そして
  • 管理されたリソースを取り除く(役に立ちたいから)。

それでは Dispose() メソッドを使用して、これらの管理対象オブジェクトを削除します。

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

そして、すべてがうまくいく。 ただし、もっといい方法がある !


もし、その人が 忘れた を呼び出す Dispose() をオブジェクトの上に置くのですか?そうすると アンマネージド のリソースを使用することができます。

漏れることはありません マネージド なぜなら、最終的にはガベージコレクタがバックグラウンドスレッドで実行され、未使用のオブジェクトに関連するメモリを解放することになるからです。これには、あなたのオブジェクトや、あなたが使っているマネージドオブジェクト(たとえば Bitmap と、その DbConnection ).

電話をかけ忘れた場合 Dispose() を使用することができます。 今も 彼らのベーコンを救え!私たちはまだ、それを呼び出す方法があります のために ガベージコレクタが最終的にオブジェクトを解放する(つまり最終化する)ときです。

<ブロッククオート

注意 ガベージコレクタは、最終的にすべての管理対象オブジェクトを解放します。 そのとき、ガベージコレクタは Finalize メソッドを実行します。GCはそのことを知りませんし、また を気にする。 あなたの 廃棄する というメソッドがあります。 これは私たちが選んだ名前で を取得するときに呼び出すメソッドです。 は、管理されていないものを処分します。

ガーベジコレクタによるオブジェクトの破壊は 完璧 は、これらの厄介なアンマネージドリソースを解放するための時間です。これを行うには Finalize() メソッドを使用します。

C#では、明示的に Finalize() メソッドを使用します。 というメソッドを書きます。 のように見えます。 a C++のデストラクタ であり の実装とみなされます。 Finalize() メソッドを使用します。

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

でも、そのコードにはバグがあるんだ。ご存知の通り、ガベージコレクタが実行されるのは バックグラウンドスレッド 2つのオブジェクトが破壊される順番はわかりません。あなたの Dispose() のコードでは マネージド オブジェクトはもう存在しないのです。

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

つまり、必要なのは Finalize() に伝える。 Dispose() べきであることを 管理されたものには一切手をつけず リソースは いないかもしれない を使用し、管理されていないリソースを解放しています。

これを実現するための標準的なパターンは Finalize()Dispose() はどちらも 第三 (!) メソッドにブール値を渡します。 Dispose() (とは対照的に Finalize() つまり、管理されたリソースを安全に解放することができるのです。

これは 内部 メソッド かもしれない のような任意の名前を付けることができますが、これは伝統的な呼び方です。 Dispose(Boolean) :

protected void Dispose(Boolean disposing)

しかし、もっと親切なパラメータ名は、こうかもしれません。

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

の実装を変更し IDisposable.Dispose() メソッドに変更します。

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

とファイナライザーを

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

備考 : を実装しているオブジェクトの子孫である場合、そのオブジェクトは Dispose を呼び出すことを忘れないでください。 ベース Disposeをオーバーライドする際に、Disposeメソッドを使用します。

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

そして、すべてがうまくいく。 ただし、もっといい方法がある !


もしユーザーが Dispose() を実行すると、すべてがクリーンアップされます。その後、ガベージコレクタがやってきてFinalizeを呼び出すと、その時点で Dispose を再び使用します。

これは無駄であるだけでなく、もしあなたのオブジェクトが、すでに廃棄したオブジェクトへのジャンクリファレンスを 最後 を呼び出すと Dispose() を使うと、また廃棄しようとするでしょ!?

私のコードでは、ディスポーザブルにしたオブジェクトへの参照を削除するように注意していることにお気づきでしょう。 Dispose をジャンクなオブジェクトの参照に置き換えます。しかし、それでも微妙なバグが忍び込むのを止めることはできませんでした。

ユーザーが Dispose() : ハンドル CursorFileBitmapIconServiceHandle(カーソルファイルビットマップアイコンサービスハンドル が破棄されます。その後、ガベージコレクタが実行されると、同じハンドルを再び破壊しようとします。

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

これを解決するには、ガベージコレクタに、わざわざオブジェクトをファイナライズする必要はない、そのリソースはすでにクリーンアップされており、これ以上の作業は必要ない、と伝えることです。これを行うには GC.SuppressFinalize() の中で Dispose() メソッドを使用します。

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

これで、ユーザーが Dispose() は、あります。

  • 解放されたアンマネージドリソース
  • 解放されたマネージドリソース

GCがファイナライザーを実行する意味はありません。すべて片付きました。

Finalize を使って、管理されていないリソースをクリーンアップすることはできないのでしょうか?

のドキュメントでは Object.Finalize は言う。

Finalizeメソッドは、オブジェクトが破壊される前に、現在のオブジェクトが保持している非管理下のリソースに対してクリーンアップ処理を実行するために使用されます。

しかし、MSDNのドキュメントには、以下のように書かれています。 IDisposable.Dispose :

管理されていないリソースの解放、解放、またはリセットに関連する、アプリケーション定義のタスクを実行します。

で、どっちなんだ?アンマネージド・リソースのクリーンアップを行うのはどちらでしょうか?その答えは、こうです。

<ブロッククオート

それはあなたの選択です! でも、選ぶのは Dispose .

確かに、ファイナライザーにアンマネージドクリーンアップを配置することは可能です。

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

この場合の問題は、ガベージコレクタがいつあなたのオブジェクトをファイナライズするかわからないということです。管理されていない、必要とされていない、使われていないネイティブのリソースは、ガベージコレクタがそのオブジェクトをファイナライズするまで、ずっと残ってしまうのです。 結局 が実行されます。そして、ファイナライザー・メソッドが呼ばれ、管理されていないリソースが一掃されます。のドキュメントでは オブジェクトのファイナライズ は、このことを指摘しています。

ファイナライザが実行される正確な時刻は不定です。クラスのインスタンスのリソースを決定論的に解放することを確実にするために、以下のように 閉じる メソッドを提供するか IDisposable.Dispose の実装が必要です。

を使うことの良さです。 Dispose を使用して、管理されていないリソースをクリーンアップします。管理されていないリソースがいつクリーンアップされるかを知ることができ、コントロールすることができます。その破壊は 決定論的である。 .


最初の質問に答えます。GCが決めた時のためではなく、なぜ今メモリを解放しないのか?私の持っている顔認識ソフトは が必要です。 530MBの内部イメージを取り除くために 現在 不要になったからです。そうしないと、マシンはスワップ停止に追い込まれる。

ボーナス・リーディング

この回答のスタイルが好きな人のために、( なぜ ということで どのように の第1章を読んでみてください。

35ページにわたり、バイナリオブジェクトを使用する際の問題点を解説し、目の前でCOMを発明しています。一旦 なぜ 残りの300ページは、マイクロソフトの実装を詳細に説明するだけである。

オブジェクトやCOMを扱ったことのあるプログラマは、最低でも第1章を読むべきだと思います。これまでで最高の解説書だ。

おまけの読み物

あなたが知っていることがすべて間違っているとき アーカイヴ by Eric Lippert

そのため、正しいファイナライザーを書くのは実に難しい。 そして を試さないことが、私ができる最善のアドバイスです。 .