ファイナライズと破棄


回答:


120

他の人はDisposeとの違いをすでにカバーしていますFinalize(ところで、Finalizeメソッドは言語仕様でデストラクタと呼ばれています)。したがって、Finalizeメソッドが役立つシナリオについて少しだけ追加します。

一部のタイプでは、使い捨てリソースを使いやすく、1回のアクションで簡単に破棄できる方法でカプセル化しています。一般的な使用法は、多くの場合、次のようになります。オープン、読み取りまたは書き込み、クローズ(破棄)。それはusing構造と非常によく適合します。

その他はもう少し難しいです。WaitEventHandlesたとえば、あるスレッドから別のスレッドに信号を送るために使用されるため、インスタンスはこのように使用されません。次に、誰がDisposeこれらを呼び出す必要があるのでしょうか?これらのような保護手段として、Finalizeインスタンスがアプリケーションによって参照されなくなったときにリソースが破棄されるようにするメソッドが実装されています。


60
私はこの承認された答えを理解できませんでした。私はまだ違いを知りたいです。それは何ですか?
Ismael

22
@Ismael:Finalize正当化される可能性のある最大の状況は、リソースの存続に関心のあるオブジェクトが多数ある場合ですが、リソースに関心がなくなったオブジェクトが、それが最後の一つ。そのような場合、Finalize通常、誰もオブジェクトに興味がないときにのみ起動します。の緩やかなタイミングはFinalize、ファイルやロックなどのファンジブルではないリソースでは恐ろしいですが、ファンジブルなリソースでは問題ない場合があります。
スーパーキャット2013年

13
素晴らしい新しい(私にとっての)単語のスーパーキャットへの+1。状況によってかなり明確になりましたが、残りの人のために、ウィキペディアでは次のように述べています。会社、債券、貴金属、または通貨。」
Jon Coombs 2013年

5
@JonCoombs:それはまあまあですが、「変更可能なリソース」という用語は、取得されるまで自由に置換可能で、解放または放棄後に再び自由に置換可能になるものに適用されることに注意する価値があります。システムにロックオブジェクトのプールがあり、コードがそれをあるエンティティに関連付けるプールを取得する場合、そのエンティティへの関連付けを目的としてそのロックへの参照を誰かが保持している限り、そのロックは、他の。ただし、保護されたエンティティを対象とするすべてのコードがロックを放棄する場合は...
スーパーキャット2015

...その後、他のエンティティに関連付けられるまで、再び自由に置換可能になります。
スーパーキャット2015

135

finalizerメソッドは、オブジェクトがガベージコレクションされたときに呼び出され、これがいつ発生するかは保証されません(強制することはできますが、パフォーマンスが低下します)。

Dispose一方、このメソッドはクラスを作成したコードによって呼び出されるため、コードが実行された瞬間に、取得したリソース(アンマネージデータ、データベース接続、ファイルハンドルなど)をクリーンアップして解放できます。あなたのオブジェクト。

標準的な方法は実装することでIDisposableありDispose、そのため、オブジェクトをusingステートメントで使用できます。などusing(var foo = new MyObject()) { }。そしてファイナライザではDispose、呼び出しコードがあなたを破棄するのを忘れた場合に備えて、を呼び出します。


17
Finalizeの実装からDisposeを呼び出す場合は、少し注意する必要があります。Disposeは、既にファイナライザでファイナライズされている可能性があるため、ファイナライザから変更したくないマネージリソースも破棄する場合があります。
itowlson 2009

6
@itowlson:オブジェクトを2回破棄できる(2番目の呼び出しは何もしない)との前提と組み合わせたnullのチェックで十分です。
サミュエル

7
オプションのマネージコンポーネントの破棄を処理するための標準のIDisposalパターンとDispose(bool)の非表示の実装は、その問題に対応しているようです。
ブロディ

デストラクタ(〜MyClass()メソッド)を実装し、常にDispose()メソッドを実装して呼び出す理由はないようです。それとも私は間違っていますか?両方を実装する必要がある場合、誰かが例を教えてくれませんか?
dpelisek

66

Finalizeは、オブジェクトを再生するときにガベージコレクターによって呼び出されるバックストップメソッドです。Disposeは「確定的なクリーンアップ」メソッドであり、アプリケーションによって呼び出され、貴重なネイティブリソース(ウィンドウハンドル、データベース接続など)が不要になったときに解放されます。

オブジェクトのユーザーとして、常にDisposeを使用します。FinalizeはGC用です。

クラスの実装者として、破棄する必要があるマネージリソースを保持している場合は、Disposeを実装します。ネイティブリソースを保持している場合は、DisposeとFinalizeの両方を実装し、どちらもネイティブリソースを解放する共通のメソッドを呼び出します。これらのイディオムは、通常、Disposeをtrueで呼び出し、Finalize呼び出しをfalseで呼び出すプライベートDispose(bool disposing)メソッドを介して結合されます。このメソッドは常にネイティブリソースを解放してから、破棄パラメーターをチェックし、trueの場合はマネージリソースを破棄してGC.SuppressFinalizeを呼び出します。


たとえば、msdn.microsoft.com /
en

2
自動クリーニング(「管理」)リソースと非自動クリーニング(「非管理」)リソースが混在するクラスの元の推奨パターンは、古くから使用されていません。より適切なパターンは、すべてのアンマネージリソースを、クリーンアップに不要なものへの強い参照を保持しない独自のマネージオブジェクトに個別にラップすることです。ファイナライズ可能なオブジェクトが直接または間接の強い参照を保持するものはすべて、GCの寿命が延長されます。クリーンアップに必要なものをカプセル化することで、不要なもののGCライフタイムの延長を回避できます。
スーパーキャット2013年

2
@JCoombs:Dispose良いですし、正しく実装するのは一般的に簡単です。 Finalize悪であり、それを正しく実装することは一般に困難です。とりわけ、GCは、そのオブジェクトへの参照が存在する限り、オブジェクトのIDが「リサイクル」されないことを保証するため、Disposable既にクリーンアップされている可能性があるオブジェクトの束をクリーンアップするのは簡単です。問題ない; オブジェクトへの参照は、どのDisposeすでに呼び出されたときにオブジェクトへの参照残るDispose既に呼ばれています。
スーパーキャット2013年

2
@JCoombs:対照的に、アンマネージリソースには、通常、そのような保証はありません。オブジェクトFredがファイルハンドル#42を所有して閉じている場合、システムは同じ番号をいくつかの他のエンティティに与えられているファイルハンドルに添付する可能性があります。その場合、ファイルハンドル#42はFredの閉じたファイルではなく、他のエンティティによってアクティブに使用されていたファイルを参照します。以下のためにFred近くのハンドル#42にしようとする再び悲惨なことでしょう。1つの管理されていないオブジェクトがまだ解放されているかどうかを100%確実に追跡することは、実行可能です。複数のオブジェクトを追跡することは非常に困難です。
スーパーキャット2013年

2
@JCoombs:すべてのアンマネージリソースが、その有効期間を制御するだけの独自のラッパーオブジェクトに配置されている場合、リソースが解放されているかどうかはわかりませんが、リソースがまだ解放されていない場合はそうであることがわかっている外部コード、ラッパーオブジェクトに安全に解放するよう要求できます。ラッパーオブジェクトは、そのようにしたかどうかを認識し、要求を実行または無視できます。GCがラッパーへの参照が常にラッパーへの有効な参照になることを保証するという事実は、非常に有用な保証です。
スーパーキャット2013年

43

ファイナライズ

  • ファイナライザは常にである必要があります。そうprotectedでないpublic場合privateは、アプリケーションのコードからメソッドを直接呼び出すことはできません。同時に、base.Finalizeメソッドを呼び出すことができます。
  • ファイナライザは、アンマネージリソースのみを解放する必要があります。
  • フレームワークは、ファイナライザが特定のインスタンスで実行されることを保証しません。
  • ファイナライザにメモリを割り当てたり、ファイナライザから仮想メソッドを呼び出したりしないでください。
  • 同期を回避し、ファイナライザで未処理の例外を発生させる。
  • ファイナライザの実行順序は非決定的です。つまり、ファイナライザ内でまだ使用可能な別のオブジェクトに依存することはできません。
  • 値型にファイナライザを定義しないでください。
  • 空のデストラクタを作成しないでください。つまり、クラスでアンマネージリソースをクリーンアップする必要がない限り、デストラクタを明示的に定義しないでください。デストラクタを定義すると、何らかの処理が行われます。後で、デストラクタのアンマネージリソースをクリーンアップする必要がなくなった場合は、完全に削除します。

廃棄

  • IDisposableファイナライザを持つすべての型に実装する
  • Disposeメソッドを呼び出した後、オブジェクトが使用不可になっていることを確認してください。つまり、Disposeメソッドが呼び出された後のオブジェクトの使用は避けてください。
  • 使い終わったらDisposeすべてのIDisposableタイプを呼び出す
  • 許可Disposeエラーを上げることなく、複数回呼び出されます。
  • Disposeメソッドを使用して、GC.SuppressFinalizeメソッド内からファイナライザへの以降の呼び出しを抑制します
  • 使い捨ての値タイプを作成しない
  • Disposeメソッド内からの例外のスローを回避する

廃棄/最終パターン

  • マイクロソフトは、両方を実装すること、DisposeおよびFinalizeアンマネージリソースを使用する場合に実装することをお勧めします。Finalize実装が実行されますと、オブジェクトがガベージコレクトされている場合、リソースはまだ開発者が呼び出しすることを怠った場合でもリリースされるDispose明示的方法を。
  • FinalizeメソッドとメソッドのアンマネージリソースをクリーンアップしDisposeます。さらに、Disposeそのクラス内のコンポーネントとして(管理されていないリソースをメンバーとして持つ).NETオブジェクトのメソッドをメソッドから呼び出しますDispose

17
私はどこでも同じ答えを読んでいますが、それでもそれぞれの目的が何であるか理解できません。私はルールを次々と読むだけで、それ以上はありません。
Ismael

@Ismael:また、MSDNからテキストをコピーして貼り付ける以外は何も追加しません。
Tarik

@tarik私はすでにそれを学びました。私がこれを尋ねたとき、私は「約束」の考えを持っていました。
Ismael

31

Finalizeは、このオブジェクトが使用されなくなったときにGCによって呼び出されます。

Disposeは、このクラスのユーザーがリソースを解放するために呼び出すことができる通常のメソッドです。

ユーザーがDisposeの呼び出しを忘れた場合、およびクラスにFinalizeが実装されている場合、GCはそれが確実に呼び出されるようにします。


3
これまで最もクリーンな答え
dariogriffo

19

本MCSD Certification Toolkit(exam 70-483)pag 193から、いくつかのキーがあります。

デストラクタ≈(ほぼ等しい)base.Finalize()、デストラクタは、デストラクタのコードを実行し、基本クラスのFinalizeメソッドを呼び出すFinalizeメソッドのオーバーライドバージョンに変換されます。その後、GCに依存しているため、いつ呼び出されるかを知ることはできません。

クラスにマネージリソースとアンマネージリソースが含まれていない場合、クラスはIDisposableデストラクタを実装または持つべきではありません。

クラスにマネージリソースのみがある場合、クラスは実装IDisposableする必要がありますが、デストラクタを持つべきではありません。(デストラクタが実行されると、管理対象オブジェクトがまだ存在するかどうか確信が持てないため、それらのDispose()メソッドを呼び出すことはできません。)

クラスにアンマネージリソースのみIDisposableがある場合は、プログラムを呼び出さない場合に備えて、クラスを実装してデストラクタを必要としますDispose()

Dispose()メソッドは、複数回実行しても安全でなければなりません。これは、変数を使用して、それが以前に実行されたことがあるかどうかを追跡することで実現できます。

Dispose()マネージリソースとアンマネージリソースの両方を解放する必要があります。

デストラクタは、アンマネージリソースのみを解放する必要があります。デストラクタが実行されると、管理対象オブジェクトがまだ存在するかどうか確信が持てないため、いずれにしてもDisposeメソッドを呼び出すことはできません。これはprotected void Dispose(bool disposing)、管理されたリソースのみが解放される(破棄される)正規パターンを使用して取得されdisposing == trueます。

リソースを解放した後、Dispose()を呼び出しGC.SuppressFinalizeて、オブジェクトがファイナライズキューをスキップできるようにする必要があります。

アンマネージリソースとマネージリソースを持つクラスの実装の例:

using System;

class DisposableClass : IDisposable
{
    // A name to keep track of the object.
    public string Name = "";

    // Free managed and unmanaged resources.
    public void Dispose()
    {
        FreeResources(true);

        // We don't need the destructor because
        // our resources are already freed.
        GC.SuppressFinalize(this);
    }

    // Destructor to clean up unmanaged resources
    // but not managed resources.
    ~DisposableClass()
    {
        FreeResources(false);
    }

    // Keep track if whether resources are already freed.
    private bool ResourcesAreFreed = false;

    // Free resources.
    private void FreeResources(bool freeManagedResources)
    {
        Console.WriteLine(Name + ": FreeResources");
        if (!ResourcesAreFreed)
        {
            // Dispose of managed resources if appropriate.
            if (freeManagedResources)
            {
                // Dispose of managed resources here.
                Console.WriteLine(Name + ": Dispose of managed resources");
            }

            // Dispose of unmanaged resources here.
            Console.WriteLine(Name + ": Dispose of unmanaged resources");

            // Remember that we have disposed of resources.
            ResourcesAreFreed = true;
        }
    }
}

2
これはいい答えです!しかし、これは間違っていると思います。「デストラクタはGC.SuppressFinalizeを呼び出す必要があります」。代わりに、パブリックDispose()メソッドでGC.SuppressFinalizeを呼び出さないでください。参照:docs.microsoft.com/en-us/dotnet/api/…このメソッドを呼び出すと、ガベージコレクターがObject.Finalize(デストラクタによってオーバーライドされます)を呼び出せなくなります。
Ewa

7

99%の確率で、どちらも心配する必要はありません。:)しかし、オブジェクトが非管理対象リソース(ウィンドウハンドル、ファイルハンドルなど)への参照を保持している場合は、管理対象オブジェクトがそれらのリソースを解放する方法を提供する必要があります。Finalizeは、リソースの解放を暗黙的に制御します。ガベージコレクタによって呼び出されます。Disposeは、リソースのリリースを明示的に制御する方法であり、直接呼び出すことができます。

ガベージコレクションのテーマについて学ぶべきことは他にもたくさんありますが、それは始まりです。


5
C#アプリケーションの1%以上がデータベースを使用していると思います。IDisposableSQLについて心配する必要がある場合です。
サミュエル

1
また、IDisposableをカプセル化する場合は、IDisposableを実装する必要があります。おそらく他の1%をカバーしています。
ダレンクラーク

@サミュエル:私はデータベースがそれとどう関係しているのか分かりません。接続を閉じることについて話している場合は問題ありませんが、それは別の問題です。タイムリーに接続を閉じるためにオブジェクトを破棄する必要はありません。
JP Alioto

1
@JP:しかし、Using(...)パターンを使用すると、対処が非常に簡単になります。
Brody

2
同意しましたが、それがまさにポイントです。usingパターンは、Disposeの呼び出しを隠します。
JP Alioto

6

ファイナライザは暗黙的なクリーンアップのためのものです。絶対にクリーンアップする必要のあるリソースをクラスが管理する場合は、これを使用する必要あります。そうしないと、ハンドルやメモリなどがリークするためです。

ファイナライザを正しく実装することは非常に困難であり、可能な限り回避する必要があります。SafeHandleクラス(.Net v2.0以降で使用可能)は、ファイナライザを実装する必要がほとんどないことを意味します。

IDisposableインターフェイスは、明示的なクリーンアップのためのものであり、はるかに一般的に使用されている-あなたは、彼らがオブジェクトを使い終わったときに、ユーザーが明示的にリソースを解放またはクリーンアップできるようにするために、これを使用する必要があります。

ファイナライザIDisposableがある場合、オブジェクトがガベージコレクションされた場合よりも早くユーザーがそれらのリソースを明示的に解放できるようにインターフェイスも実装する必要があることに注意してください。

ファイナライザとに関する推奨事項の最良かつ最も完全なセットであると私が考えるものについては、DG Update:Dispose、Finalization、およびResource Managementを参照してくださいIDisposable


3

要約は-

  • アンマネージリソースへの参照があるクラスのファイナライザーを記述し、そのクラスのインスタンスがガベージコレクションによって自動的に解放されたときに、アンマネージリソースが解放されるようにしたい場合 。オブジェクトのFinalizerを明示的に呼び出すことはできないことに注意してください。Finalizerは、ガベージコレクターによって、必要と見なされたときに自動的に呼び出されます。
  • 一方、クラスがアンマネージリソースへの参照を持っているが、ガベージコレクターが起動するのを待たない場合は、IDisposableインターフェイスを実装します(その結果、クラスの結果としてDispose()メソッドを定義します)。 (これはいつでも可能です-プログラマーの制御下ではありません)そして、あなたが終わったらすぐにそれらのリソースを解放したいです。したがって、オブジェクトのDispose()メソッドを呼び出すことにより、アンマネージリソースを明示的に解放できます。

また、もう1つの違いは、Dispose()実装では、マネージリソースも解放する必要があることですが、ファイナライザでは行わないでください。これは、オブジェクトが参照する管理対象リソースが、ファイナライズの準備が整う前にすでにクリーンアップされている可能性が高いためです。

アンマネージリソースを使用するクラスの場合、ベストプラクティスは、開発者がオブジェクトを明示的に破棄するのを忘れた場合のフォールバックとして使用するDispose()メソッドとFinalizerの両方を定義することです。どちらも共有メソッドを使用して、マネージリソースとアンマネージリソースをクリーンアップできます。

class ClassWithDisposeAndFinalize : IDisposable
{
    // Used to determine if Dispose() has already been called, so that the finalizer
    // knows if it needs to clean up unmanaged resources.
     private bool disposed = false;

     public void Dispose()
     {
       // Call our shared helper method.
       // Specifying "true" signifies that the object user triggered the cleanup.
          CleanUp(true);

       // Now suppress finalization to make sure that the Finalize method 
       // doesn't attempt to clean up unmanaged resources.
          GC.SuppressFinalize(this);
     }
     private void CleanUp(bool disposing)
     {
        // Be sure we have not already been disposed!
        if (!this.disposed)
        {
             // If disposing equals true i.e. if disposed explicitly, dispose all 
             // managed resources.
            if (disposing)
            {
             // Dispose managed resources.
            }
             // Clean up unmanaged resources here.
        }
        disposed = true;
      }

      // the below is called the destructor or Finalizer
     ~ClassWithDisposeAndFinalize()
     {
        // Call our shared helper method.
        // Specifying "false" signifies that the GC triggered the cleanup.
        CleanUp(false);
     }

2

私が知っている最良の例。

 public abstract class DisposableType: IDisposable
  {
    bool disposed = false;

    ~DisposableType()
    {
      if (!disposed) 
      {
        disposed = true;
        Dispose(false);
      }
    }

    public void Dispose()
    {
      if (!disposed) 
      {
        disposed = true;
        Dispose(true);
        GC.SuppressFinalize(this);
      }
    }

    public void Close()
    {
      Dispose();
    }

    protected virtual void Dispose(bool disposing)
    {
      if (disposing) 
      {
        // managed objects
      }
      // unmanaged objects and resources
    }
  }

2

C#のFinalizeメソッドとDisposeメソッドの違い。

GCはfinalizeメソッドを呼び出して、アンマネージリソース(ファイル操作、Windows API、ネットワーク接続、データベース接続など)を再利用しますが、GCが呼び出す時間は固定されていません。これはGCによって暗黙的に呼び出されます。つまり、低レベルの制御はありません。

Disposeメソッド:コードから呼び出すため、低レベルの制御があります。アンマネージリソースは使用できないと感じたときにいつでも再利用できます。IDisposalパターンを実装することでこれを実現できます。


1

多くの場合、クラスインスタンスは、ウィンドウハンドル(HWND)、データベース接続など、ランタイムによって管理されないリソースに対する制御をカプセル化します。したがって、これらのリソースを解放するための明示的な方法と暗黙的な方法の両方を提供する必要があります。オブジェクトに保護されたFinalizeメソッドを実装することにより、暗黙的な制御を提供します(C#のデストラクタ構文およびC ++のマネージ拡張)。ガベージコレクターは、オブジェクトへの有効な参照がなくなった後のある時点でこのメソッドを呼び出します。場合によっては、オブジェクトを使用するプログラマーに、ガベージコレクターがオブジェクトを解放する前に、これらの外部リソースを明示的に解放する機能を提供することができます。外部リソースが不足している、または高価な場合、リソースが使用されなくなったときにプログラマが明示的にリソースを解放すると、パフォーマンスが向上します。明示的な制御を提供するには、IDisposableインターフェイスによって提供されるDisposeメソッドを実装します。オブジェクトの使用者は、オブジェクトを使用して完了したときにこのメソッドを呼び出す必要があります。オブジェクトへの他の参照が有効であっても、Disposeを呼び出すことができます。

Disposeを使用して明示的な制御を提供する場合でも、Finalizeメソッドを使用して暗黙的なクリーンアップを提供する必要があることに注意してください。Finalizeは、プログラマーがDisposeの呼び出しに失敗した場合にリソースが永続的にリークするのを防ぐためのバックアップを提供します。


1

DisposeとFinalizeの主な違いは次のとおりです。

Dispose通常、コードによって呼び出されます。リソースは、呼び出すとすぐに解放されます。人々はメソッドの呼び出しを忘れているので、using() {}ステートメントが発明されました。プログラムが内のコードの実行を完了すると、自動的にmethod {}が呼び出されDisposeます。

Finalizeコードによって呼び出されません。ガベージコレクター(GC)によって呼び出されることを意味します。つまり、GCがそうすることを決定したときはいつでも、リソースは将来いつでも解放される可能性があります。GCが機能するとき、GCは多くのFinalizeメソッドを通過します。これに重いロジックがあると、プロセスが遅くなります。プログラムのパフォーマンスの問題が発生する可能性があります。そこに何を入れるか注意してください。

個人的には、破棄ロジックのほとんどをDisposeで記述します。うまくいけば、これで混乱が解消されます。


-1

私たちが知っているように、disposeとfinalizeは両方とも管理されていないリソースを解放するために使用されます。


Disposeはすぐにリソースを解放します。Finalizeは、リソースを適時に解放する場合と解放しない場合があります。
スーパーキャット2015

1
:ああ、彼はおそらく、これは、より多くのここまで読んで「ファイナライズ可能オブジェクトがメモリがクリアされる前に二回GCで検出する必要がある」という意味ericlippert.com/2015/05/18/...
aeroson

-4

最初の部分に答えるには、まったく同じクラスオブジェクトに対して人々が異なるアプローチを使用する例を提供する必要があります。そうでなければ、答えるのは難しい(または奇妙なことですらある)。

2番目の質問については、より良い最初に、この読み IDisposableインターフェイスを適切に使用し ている主張

それはあなたの選択です!しかし、Disposeを選択します。

つまり、GCはファイナライザ(存在する場合)のみを認識します。Microsoftのデストラクタとしても知られています。優れたコードは、両方(ファイナライザとDispose)からクリーンアップを試みます。

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