web-dev-qa-db-ja.com

.NETにはガベージコレクターがあるのに、なぜファイナライザー/デストラクタ/ディスポーズパターンが必要なのですか?

私が正しく理解していれば、.netランタイムは常に私の後にクリーンアップされます。したがって、新しいオブジェクトを作成し、コードでそれらを参照するのをやめると、ランタイムはそれらのオブジェクトをクリーンアップし、それらが占有していたメモリを解放します。

これが事実なので、なぜいくつかのオブジェクトはデストラクタまたはdisposeメソッドを持っている必要がありますか?それらが参照されなくなったときに、ランタイムはそれらの後でクリーンアップしませんか?

67
J A

ファイナライザーは、ファイルハンドル、ソケット、カーネルオブジェクトなど、不足しているリソースがシステムに解放されることを保証するために必要です。ファイナライザーは常にオブジェクトの寿命の終わりに実行されるため、これらのハンドルを解放するための指定された場所です。

Disposeパターンは、リソースの決定論的破壊を提供するために使用されます。 .netランタイムガベージコレクターは非決定論的であるため(つまり、ランタイムが古いオブジェクトを収集してファイナライザーを呼び出すタイミングがわからない)、システムリソースの決定論的リリースを保証するメソッドが必要でした。したがって、Disposeパターンを適切に実装すると、リソースの決定論的な解放が提供され、コンシューマーが不注意でオブジェクトを破棄しない場合、ファイナライザーはオブジェクトをクリーンアップします。

Disposeが必要な理由の簡単な例は、迅速でダーティなログメソッドです。

public void Log(string line)
{
    var sw = new StreamWriter(File.Open(
        "LogFile.log", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None));

    sw.WriteLine(line);

    // Since we don't close the stream the FileStream finalizer will do that for 
    // us but we don't know when that will be and until then the file is locked.
}

上記の例では、ガベージコレクターがStreamWriterオブジェクトのファイナライザーを呼び出すまでファイルはロックされたままになります。その間、ログを書き込むためにメソッドが再度呼び出される可能性があるため、これには問題がありますが、ファイルがまだロックされているため、今回は失敗します。

正しい方法は、オブジェクトの使用が終了したら、オブジェクトを破棄することです。

public void Log(string line)
{
    using (var sw = new StreamWriter(File.Open(
        "LogFile.log", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))) {

        sw.WriteLine(line);
    }

    // Since we use the using block (which conveniently calls Dispose() for us)
    // the file well be closed at this point.
}

ところで、技術的にファイナライザーとデストラクタは同じ意味です。私はc#デストラクタを「ファイナライザ」と呼ぶことを好みます。そうしないと、C#とは異なり、決定論的であるC++デストラクタと人々を混同する傾向があるためです。

93
Yona

前の答えは良いですが、ここでもう一度重要な点を強調させてください。特に、あなたはそれを言いました

私が正しく理解していれば、.netランタイムは常に私の後にクリーンアップされます。

これは部分的に正しいだけです。実際、。NETonlyは、1つの特定のリソースの自動管理を提供します:メインメモリ。他のすべてのリソースは手動でクリーンアップする必要があります。1)

奇妙なことに、メインメモリはプログラムリソースに関するほとんどすべての議論で特別なステータスを取得します。もちろん、これには正当な理由があります。メインメモリが最も不足しているリソースであることがよくあります。ただし、他の種類のリソースもあり、それらも管理する必要があることを覚えておく価値があります。


1) 通常試みられる解決策は、他のリソースの存続期間を、コード内のメモリ位置または識別子の存続期間に結合することです。したがって、ファイナライザーが存在します。

20
Konrad Rudolph

ガベージコレクタは、システムが実際にメモリを解放する必要がない限り、システムにメモリ不足がない場合にのみ実行されます。つまり、GCがいつ実行されるかを確信することはできません。

ここで、あなたがデータベース接続であると想像してください。後でGCをクリーンアップすると、データベースに必要以上に長く接続され、奇妙なロード状況が発生する可能性があります。その場合、IDisposableを実装して、ユーザーがDispose()を呼び出すか、using()を使用して、後で実行される可能性のあるGCに依存することなく、接続ができるだけ早く閉じられるようにします。

通常、IDisposableは、管理されていないリソースで機能するすべてのクラスに実装されます。

9
Michael Stum
  1. ガベージコレクターがあなたの後にクリーンアップできないことがあります
  2. それでもcanクリーンアップしても、より早くクリーンアップするのに役立ちます
4
orip

本当の理由は、.netガベージコレクションが収集するように設計されていないためです管理されていないリソースしたがって、これらのリソースのクリーンアップは依然として開発者の手に委ねられています。また、オブジェクトがスコープ外になったときに、オブジェクトファイナライザーが自動的に呼び出されることはありません。それらは、不確定な時間にGCによって呼び出されます。そして、それらが呼び出されると、GCはすぐにそれを実行せず、次のラウンドがそれを呼び出すのを待ち、クリーンアップする時間をさらに増やします。オブジェクトが管理されていないリソース(ファイルなど)をほとんど保持していない場合は、良いことではありません。またはネットワーク接続)。使い捨てパターンを入力します。開発者は、決められた時間に(yourobject.Dispose()またはusing(...)ステートメントを呼び出すときに)不足しているリソースを手動で解放できます。 GC.SuppressFinalize(this);を呼び出す必要があることに注意してください。 disposeメソッドで、オブジェクトが手動で破棄され、ファイナライズされるべきではないことをGCに通知します。 K.CwalinaとB.AbramsによるFrameworkDesignGuidelinesの本をご覧になることをお勧めします。 Disposableパターンが非常によく説明されています。

幸運を!

2

簡単な説明:

  • Disposeは、deterministic非メモリリソース、特に乏しいリソースの廃棄用に設計されています。たとえば、ウィンドウハンドルやデータベース接続などです。
  • Finalizeは、non-deterministic非メモリリソースの破棄用に設計されています。通常、Disposeが呼び出されなかった場合のバックストップとして使用されます。

Finalizeメソッドを実装するためのいくつかのガイドライン:

  • Finalizeメソッドにはパフォーマンスコストが伴うため、Finalizeはファイナライズが必要なオブジェクトにのみ実装してください。
  • Finalizeメソッドが必要な場合は、IDisposableを実装して、タイプのユーザーがFinalizeメソッドを呼び出すコストを回避できるようにすることを検討してください。
  • Finalizeメソッドは、パブリックではなく保護する必要があります。
  • Finalizeメソッドは、型が所有するすべての外部リソースを解放する必要がありますが、のみ所有する外部リソースです。他のリソースを参照するべきではありません。
  • CLRは、Finalizeメソッドが呼び出される順序については保証しません。 Danielがコメントで述べているように、これは、Finalizeメソッドがメンバー参照型にアクセスできないことを意味します。これらは独自のファイナライザーを持っている(またはいつか持っている)可能性があるためです。
  • 型の基本型以外の型でFinalizeメソッドを直接呼び出さないでください。
  • Finalizeメソッドで未処理の例外を回避するようにしてください。これにより、プロセスが終了します(2.0以降)。
  • Finalizerメソッドで長時間実行されるタスクを実行しないでください。これにより、Finalizerスレッドがブロックされ、他のFinalizerメソッドが実行されなくなります。

Disposeメソッドを実装するためのいくつかのガイドライン:

  • 明示的に解放する必要があるリソースをカプセル化するタイプに、disposeデザインパターンを実装します。
  • 基本タイプが保持していない場合でも、リソースを保持する1つ以上の派生型がある基本タイプにdisposeデザインパターンを実装します。
  • インスタンスでDisposeが呼び出された後、GC.SuppressFinalizeメソッドを呼び出して、Finalizeメソッドが実行されないようにします。このルールの唯一の例外は、Disposeでカバーされていない作業をFinalizeで実行する必要があるというまれな状況です。
  • Disposeが呼び出されると想定しないでください。型が所有するアンマネージリソースも、Disposeが呼び出されない場合に備えて、Finalizeメソッドで解放する必要があります。
  • リソースがすでに破棄されている場合は、このタイプ(Dispose以外)のインスタンスメソッドからObjectDisposedExceptionをスローします。このルールは、例外をスローせずに複数回呼び出すことができる必要があるため、Disposeメソッドには適用されません。
  • 基本タイプの階層を介してDisposeの呼び出しを伝播します。 Disposeメソッドは、このオブジェクトとこのオブジェクトが所有するすべてのオブジェクトが保持するすべてのリソースを解放する必要があります。
  • Disposeメソッドが呼び出された後は、オブジェクトを使用できないようにすることを検討する必要があります。すでに破棄されているオブジェクトを再作成することは、実装が難しいパターンです。
  • 例外をスローせずに、Disposeメソッドを複数回呼び出すことができるようにします。このメソッドは、最初の呼び出しの後は何もしません。
2
RoadWarrior

デスクリュクタとdisposeメソッドを必要とするオブジェクトは、管理されていないリソースを使用しています。したがって、ガベージコレクタはこれらのリソースをクリーンアップできず、自分でこれを行う必要があります。

IDisposableについてはMSDNのドキュメントを参照してください。 http://msdn.Microsoft.com/en-us/library/system.idisposable.aspx

この例では、管理されていないハンドラーであるIntPrを使用しています。

1
MartinHN

一部のオブジェクトは、低レベルのアイテムをクリーンアップする必要がある場合があります。閉じる必要のあるハードウェアなど。

0
Brian Knoblauch

主に非マネージコード、および非マネージコードとの相互作用のため。 「純粋な」マネージコードにはファイナライザーは必要ありません。一方、使い捨ては、使い終わったときに何かを強制的に解放するための便利なパターンです。

0
Jeff Mc

.NETガベージコレクターは、.NETランタイム内で管理対象オブジェクトを処理する方法を知っています。ただし、Disposeパターン(IDisposable)は、主にアプリケーションが使用している管理対象外のオブジェクトに使用されます。

言い換えると、.NETランタイムは、すべての種類のデバイスを処理する方法や、そこにある処理(ネットワーク接続、ファイルハンドル、グラフィックスデバイスなどを閉じる)を必ずしも知っているわけではないため、IDisposableを使用すると、「タイプに「自分のクリーンアップを実装する」。その実装を確認すると、ガベージコレクターはDispose()を呼び出して、マネージヒープ外のものが確実にクリーンアップされるようにすることができます。

0
Jeff Donnici

純粋な管理対象オブジェクトが使用されなくなったときに特定のアクションを実行する必要がある場合がいくつかあります(ごくわずかです)。頭のてっぺんから例を思い付くことができませんが、いくつか見ました。長年にわたる合法的な使用の。ただし、主な理由は、オブジェクトが使用している可能性のある管理されていないリソースをクリーンアップすることです。

したがって、一般に、管理されていないリソースを使用している場合を除き、Dispose/Finalizeパターンを使用する必要はありません。

0
Stephen Martin

ガベージコレクタは、管理環境が割り当てなかったものを収集できないためです。したがって、メモリ割り当てが発生するアンマネージAPIの呼び出しは、従来の方法で収集する必要があります。

0
Quibblesome