ガベージコレクターはIDisposable.Disposeを呼び出しますか?


134

.NET IDisposableパターン は、ファイナライザを作成してIDisposableを実装する場合、ファイナライザが明示的にDisposeを呼び出す必要があること意味します。これは論理的であり、ファイナライザーが保証されているまれな状況で私が常に行ってきたことです。

ただし、これを実行するとどうなりますか?

class Foo : IDisposable
{
     public void Dispose(){ CloseSomeHandle(); }
}

ファイナライザなどは実装しないでください。フレームワークはDisposeメソッドを呼び出しますか?

はい、これは馬鹿げているように聞こえることに気づき、すべての論理はそれが意味をなさないことを意味しますが、私はいつも頭の後ろに2つのものを持っていて、私を不確かにしました。

  1. 数年前に誰かが実際にこれを行うと私に言ったことがあり、その人は「自分のことを知っている」という非常に確かな実績を持っています。

  2. コンパイラー/フレームワークは、実装するインターフェース(foreach、拡張メソッド、属性に基づくシリアライゼーションなど)に応じて他の「魔法」のことを行うため、これも「魔法」である可能性があります。

私はそれについて多くのことを読みましたし、暗示されていることはたくさんありますが、この質問に対する明確な「はい」または「いいえ」の答えを見つけることはできませんでした。

回答:


121

.Netガベージコレクターは、ガベージコレクションでオブジェクトのObject.Finalizeメソッドを呼び出します。デフォルトこれは行わず、何も、あなたは追加のリソースを解放する場合がオーバーライドする必要があります。

Disposeは自動的には呼び出されず、「using」ブロックや「try finally」ブロック内などでリソースを解放する場合は明示的に呼び出す必要があります

詳細については、http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx参照してください


35
実際、オーバーライドされていない場合、GCがObject.Finalizeを呼び出すとはまったく思いません。オブジェクトはファイナライザーを実質的に持たないと判断され、ファイナライズは抑制されます。これは、オブジェクトがファイナライズ/リーチ可能なキューにある必要がないため、より効率的になります。
ジョンスキート

7
MSDNのとおり:msdn.microsoft.com/en-us/library/…C#で Object.Finalizeメソッドを実際に「オーバーライド」することはできません。コンパイラーはエラーを生成します:object.Finalizeをオーバーライドしないでください。代わりに、デストラクタを提供します。; つまり、ファイナライザとして効果的に機能するデストラクタを実装する必要があります。[これは受け入れられた回答であり、最もよく読まれる可能性があるため、完全を
期す

1
GCは、ファイナライザをオーバーライドしないオブジェクトに対しては何もしません。ファイナライズキューには置かれません。ファイナライザーは呼び出されません。
Dave Black

1
@dotnetguy-元のC#仕様では「デストラクタ」について言及していますが、実際にはファイナライザと呼ばれています。そのメカニズムは、真の「デストラクタ」がアンマネージ言語で機能する方法とはまったく異なります。
Dave Black

67

コメントの中でブライアンの要点を強調したい。それは重要だからだ。

ファイナライザは、C ++のような確定的なデストラクタではありません。他の人が指摘したように、それはされます場合は、十分なメモリを持っている場合は、確かにそこにそれが呼び出されるときの保証はなく、これまでと呼ばれます。

しかし、ファイナライザの悪い点は、ブライアンが言ったように、オブジェクトがガベージコレクションを存続させることです。これは悪いことです。どうして?

ご存じかもしれませんが、GCは世代に分割されています-世代0、1、2とラージオブジェクトヒープ。スプリットは緩やかな用語です。1ブロックのメモリを取得しますが、Gen 0オブジェクトの開始位置と終了位置のポインタがあります。

思考プロセスは、寿命の短いオブジェクトをたくさん使用する可能性が高いということです。したがって、GCがGen 0オブジェクトに到達するには、これらが簡単かつ高速である必要があります。したがって、メモリのプレッシャーがある場合、最初に行うのはGen 0コレクションです。

これで十分な圧力が解決されない場合は、戻ってGen 1スイープ(Gen 0を再実行)し、それでも十分でない場合は、Gen 2スイープ(Gen 1とGen 0を再実行)を実行します。したがって、存続期間の長いオブジェクトのクリーンアップには時間がかかり、かなりのコストがかかる可能性があります(操作中にスレッドが中断される可能性があるため)。

これは、次のようなことをすると、

~MyClass() { }

オブジェクトは、何があっても、ジェネレーション2で使用されます。これは、GCがガベージコレクション中にファイナライザを呼び出す方法がないためです。そのため、ファイナライズが必要なオブジェクトは、別のスレッド(ファイナライザスレッド-強制終了するとあらゆる種類の悪いことが発生する)によってクリーンアップされる特別なキューに移動されます。これは、オブジェクトの滞留時間が長くなり、ガベージコレクションを強制する可能性があることを意味します。

つまり、IDisposableを使用してリソースを可能な限りクリーンアップし、ファイナライザの使用方法を真剣に模索する必要があるということを理解するだけです。これは、アプリケーションの最大の利益になります。


8
可能な限りIDisposableを使用することに同意しますが、disposeメソッドを呼び出すファイナライザも必要です。disposeメソッドを呼び出した後、IDispose.DisposeでGC.SuppressFinalize()を呼び出して、オブジェクトがファイナライザーキューに入れられないようにすることができます。
jColeson

2
世代には、1〜3ではなく0〜2の番号が付けられますが、それ以外の点では投稿は良好です。ただし、オブジェクトによって参照されるオブジェクト、またはオブジェクトによって参照されるオブジェクトなども、別の世代のガベージコレクションから保護されます(ファイナライズに対してではありません)。したがって、ファイナライザを持つオブジェクトは、ファイナライゼーションに不要なものへの参照を保持するべきではありません。
スーパーキャット


3
「あなたのオブジェクトは、何があっても、第2世代まで生きます。」これは非常に基本的な情報です!これにより、ファイナライズのために「準備」された短期のGen2オブジェクトが多数存在するシステムのデバッグにかかる​​時間を大幅に節約できましたが、ヒープの使用量が多いため、ファイナライズされなかったためOutOfMemoryExceptionが発生しませんでした。(空の)ファイナライザを削除し、コードを別の場所に移動(回避)すると、問題は解消され、GCは負荷を処理できました。
シャープナー

@CoryFoy「あなたのオブジェクトは、何があっても、第2世代まで生きます」これに関するドキュメントはありますか?
Ashish Negi

33

すでに多くの良い議論がここにあり、私はパーティーに少し遅れましたが、私はいくつかのポイントを自分で付け加えたかったです。

  • ガベージコレクションは、Disposeメソッドを直接実行することはありません。
  • GC は、必要に応じてファイナライザ実行します。
  • ファイナライザを持つオブジェクトに使用される一般的なパターンの1つは、Dispose(bool disposing)として定義されているメソッドを呼び出し、falseを渡すことで、明示的なDispose呼び出しではなく、ファイナライズにより呼び出しが行われたことを示します。
  • これは、オブジェクトをファイナライズする際に、他の管理対象オブジェクトに関する仮定を行うのは安全ではないためです(それらはすでにファイナライズされている可能性があります)。

class SomeObject : IDisposable {
 IntPtr _SomeNativeHandle;
 FileStream _SomeFileStream;

 // Something useful here

 ~ SomeObject() {
  Dispose(false);
 }

 public void Dispose() {
  Dispose(true);
 }

 protected virtual void Dispose(bool disposing) {
  if(disposing) {
   GC.SuppressFinalize(this);
   //Because the object was explicitly disposed, there will be no need to 
   //run the finalizer.  Suppressing it reduces pressure on the GC

   //The managed reference to an IDisposable is disposed only if the 
   _SomeFileStream.Dispose();
  }

  //Regardless, clean up the native handle ourselves.  Because it is simple a member
  // of the current instance, the GC can't have done anything to it, 
  // and this is the onlyplace to safely clean up

  if(IntPtr.Zero != _SomeNativeHandle) {
   NativeMethods.CloseHandle(_SomeNativeHandle);
   _SomeNativeHandle = IntPtr.Zero;
  }
 }
}

これはシンプルなバージョンですが、このパターンにつまずく可能性がある多くのニュアンスがあります。

  • IDisposable.Disposeのコントラクトは、複数回呼び出しても安全である必要があることを示しています(既に破棄されたオブジェクトでDisposeを呼び出しても何も実行されません)。
  • 使い捨てオブジェクトの継承階層を適切に管理することは非常に複雑になる可能性があります。特に、さまざまなレイヤーが新しい使い捨ておよび管理対象外のリソースを導入する場合はなおさらです。上記のパターンでは、Dispose(bool)を仮想的にオーバーライドして管理できるようにしていますが、エラーが発生しやすいことがわかりました。

私の意見では、使い捨ての参照とファイナライズを必要とする可能性のあるネイティブリソースの両方を直接含む型を完全に回避することをお勧めします。SafeHandlesは、独自のファイナライズを内部で提供するネイティブリソースをディスポーザブルにカプセル化することにより、これを実行する非常にクリーンな方法を提供します(非同期例外のためにネイティブハンドルが失われる可能性があるP / Invoke中にウィンドウを削除するなど、他の多くの利点もあります)。 。

SafeHandleを定義するだけでこれは簡単になります。


private class SomeSafeHandle
 : SafeHandleZeroOrMinusOneIsInvalid {
 public SomeSafeHandle()
  : base(true)
  { }

 protected override bool ReleaseHandle()
 { return NativeMethods.CloseHandle(handle); }
}

包含型を次のように簡略化できます。


class SomeObject : IDisposable {
 SomeSafeHandle _SomeSafeHandle;
 FileStream _SomeFileStream;
 // Something useful here
 public virtual void Dispose() {
  _SomeSafeHandle.Dispose();
  _SomeFileStream.Dispose();
 }
}

1
SafeHandleZeroOrMinusOneIsInvalidクラスはどこから来たのですか?組み込みの.netタイプですか?
オリオンエドワーズ

+1 for //私の意見では、ファイナライズを必要とする可能性のある使い捨て参照とネイティブリソースの両方を直接含む型を持つことは完全に回避する方がはるかに良いです。ファイナライズ。
スーパーキャット2011


1
GC.SuppressFinalizeこの例での呼び出しについて。このコンテキストでは、SuppressFinalizeは、Dispose(true)正常に実行された場合にのみ呼び出す必要があります。場合はDispose(true)ファイナライズが抑制された後、いくつかの時点で失敗しますが、すべてのリソース(特に管理されていないもの)がクリーンアップされる前に、あなたはまだファイナライズができるだけクリーンアップとして行うために発生します。へのGC.SuppressFinalize呼び出しのDispose()後、呼び出しをメソッドに移動することをお勧めしDispose(true)ます。フレームワーク設計ガイドラインこの投稿を参照してください。
BitMask777 2012年

6

私はそうは思いません。Disposeが呼び出されるタイミングを制御できます。つまり、理論的には、(たとえば)他のオブジェクトの存在について仮定を行う廃棄コードを記述できます。ファイナライザがいつ呼び出されるかを制御することはできないため、ファイナライザが自動的にDisposeを呼び出すようにするのは危険です。


編集:私は立ち去ってテストしましたが、念のため:

class Program
{
    static void Main(string[] args)
    {
        Fred f = new Fred();
        f = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Fred's gone, and he's not coming back...");
        Console.ReadLine();
    }
}

class Fred : IDisposable
{
    ~Fred()
    {
        Console.WriteLine("Being finalized");
    }

    void IDisposable.Dispose()
    {
        Console.WriteLine("Being Disposed");
    }
}

特にファイナライズ時には、処分中にユーザーが利用できるオブジェクトについて想定することは危険であり、巧妙に行われる可能性があります。
スコットドーマン

3

あなたが説明するケースではありませんが、GCはファイナライザを持っている場合は、それを呼び出します。

しかしながら。次のガベージコレクションでは、オブジェクトは収集されるのではなく、ファイナライズキューに入れられ、すべてが収集されて、ファイナライザーが呼び出されます。その後の次のコレクションは解放されます。

アプリのメモリプレッシャーによっては、しばらくの間、そのオブジェクト生成のGCがない場合があります。したがって、たとえばファイルストリームまたはdb接続の場合、ファイナライザコールでアンマネージリソースが解放されるまでしばらく待たなければならない場合があり、いくつかの問題が発生します。


1

いいえ、呼び出されません。

ただし、オブジェクトを破棄することを忘れないようにするのは簡単です。usingキーワードを使用してください。

これについて次のテストを行いました。

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo();
        foo = null;
        Console.WriteLine("foo is null");
        GC.Collect();
        Console.WriteLine("GC Called");
        Console.ReadLine();
    }
}

class Foo : IDisposable
{
    public void Dispose()
    {

        Console.WriteLine("Disposed!");
    }

1
これは、<code> using </ code>キーワードを使用しない場合は呼び出されない例です。このスニペットには9歳の誕生日があります。
penyaskito 2017

1

GCは破棄を呼び出しません。それはかもしれあなたのファイナライザを呼び出して、それでもこれは、すべての状況下で保証するものではありません。

これを処理する最良の方法については、この記事を参照してください。


0

IDisposableのドキュメントには、動作のかなり明確で詳細な説明とコード例が記載されています。GCはDispose()インターフェイス上のメソッドを呼び出しませんが、オブジェクトのファイナライザを呼び出します。


0

IDisposableパターンは、主に開発者が呼び出すために作成されました。IDisposeを実装するオブジェクトがある場合、開発者はusingオブジェクトのコンテキストの周りにキーワードを実装するか、Disposeメソッドを直接呼び出す必要があります。

パターンのフェイルセーフは、Dispose()メソッドを呼び出すファイナライザーを実装することです。そうしないと、メモリリークが発生する可能性があります。つまり、COMラッパーを作成し、System.Runtime.Interop.Marshall.ReleaseComObject(comObject)(Disposeメソッドに配置される)を呼び出さない場合です。

ファイナライザーを含むオブジェクトを追跡し、GCによってファイナライザーテーブルに格納し、GCによってクリーンアップヒューリスティックが起動したときにそれらを呼び出す以外に、clrにはDisposeメソッドを自動的に呼び出す魔法はありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.