141

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

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

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

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

はい、私はこれがばかげているように聞こえることを理解しています、そしてすべての論理はそれがそうではないことを意味します、しかし私はいつも頭の後ろに2つのものを持っていました。

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

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

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

4

9 に答える 9

127

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

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

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

于 2008-09-05T00:40:06.167 に答える
71

ブライアンのコメントは重要なので強調したいと思います。

ファイナライザは、C++ のような決定論的なデストラクタではありません。他の人が指摘しているように、いつ呼び出されるかという保証はありません。実際、十分なメモリがあれば、呼び出される場合もあります。

しかし、ファイナライザーの悪い点は、Brian が言ったように、オブジェクトがガベージ コレクションに耐えられることです。これは悪いことがあります。なんで?

ご存じないかもしれませんが、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 を使用してリソースをクリーンアップし、ファイナライザーの使用を回避する方法を真剣に見つけようとする点を強調するためのものです。それはあなたのアプリケーションにとって最善の利益です。

于 2008-09-05T01:52:46.850 に答える
34

ここにはすでに多くの良い議論があり、パーティーに少し遅れてしまいましたが、自分でいくつかのポイントを追加したいと思いました.

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

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();
 }
}
于 2008-09-05T11:07:14.940 に答える
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");
    }
}
于 2008-09-05T00:37:22.803 に答える
4

あなたが説明する場合ではありませんが、ファイナライザーがある場合は、GCがファイナライザーを呼び出します。

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

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

于 2008-09-05T00:40:04.480 に答える
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!");
    }
于 2008-09-05T00:40:12.763 に答える
1

GCはdisposeを呼び出しません。ファイナライザーと呼ばれることもありますが、これでもすべての状況で保証されるわけではありません。

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

于 2008-09-05T00:42:47.767 に答える
0

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

于 2008-09-05T00:47:07.600 に答える
0

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

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

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

于 2008-09-05T07:31:24.117 に答える