いいえ、使い捨てクラスをラップしているという理由だけで、使い捨てクラスにファイナライザーを実装しないでください。
Dispose
クラスとファイナライザーのクリーンアップシナリオが3つあると考えてください。
Dispose()
と呼ばれます。
- ファイナライザーは、アプリケーションのシャットダウン時に呼び出されます。
- オブジェクトは収集される予定でしたが、ファイナライザーは抑制されていませんでした(ほとんどの場合、への呼び出しから
Dispose()
ですが、クリーンアップする必要がない状態になった場合は、常にファイナライザーを抑制してください。 、および必要な状態になっている場合は再登録します(たとえば、メソッドのOpen()
/Close()
ペアがある場合)。
ここで、管理されていないリソースを直接管理している場合(たとえば、を介したハンドルIntPtr
)、これらの3つのシナリオでは、2つのクリーンアップ方法のいずれかが直接呼び出され、クリーンアップが必要な3つのシナリオと直接一致します。
わかった。したがって、「外部」クラスにファイナライザーが正しく実装されている使い捨てラッピングを考えてみましょう。
~MyClass()
{
// This space deliberately left blank.
}
ファイナライザーは、処理するための管理されていないクリーンアップがないため、何もしません。唯一の効果は、このnullファイナライザーが抑制されていない場合、ガベージコレクション時にファイナライザーキューに入れられ、フィールドを介してのみ到達可能なものがすべて有効になり、最終的にはファイナライズに昇格することです。スレッドはこのnopメソッドを呼び出し、ファイナライズ済みとしてマークし、ガベージコレクションの対象になります。ただし、昇格されたため、Gen0の場合はGen1になり、Gen1の場合はGen2になります。
ファイナライズする必要があった実際のオブジェクトもプロモートされ、収集だけでなくファイナライズも少し長く待つ必要があります。何があっても、最終的には第2世代になります。
さて、それは十分に悪いことです。たとえば、ファイナライズ可能なクラスを保持するフィールドで何かを実行するコードをファイナライザーに実際に配置したとします。
待って。何しようか?ファイナライザーを直接呼び出すことはできないため、破棄します。ああ、待ってください。このクラスのファイナライザーの動作Dispose()
とファイナライザーの動作は、安全であるほど十分に近いと確信していますか?ファイナライザーではなくdisposeメソッドで処理しようとする弱参照を介して、一部のリソースを保持していないことをどのようにして知ることができますか?そのクラスの作成者は、ファイナライザー内から弱参照を処理することの危険性についてすべて知っていますが、Dispose()
他の誰かのファイナライザーメソッドの一部ではなく、メソッドを作成していると考えていました。
次に、アプリケーションのシャットダウン中に外部ファイナライザーが呼び出された場合はどうなりますか。内側のファイナライザーがまだ呼び出されていないことをどうやって知っていますか?クラスの作成者がDispose()
メソッドを書いているとき、彼らは「わかりました。ファイナライザーがすでに実行された後に呼び出された場合を処理することを確認しましょう。このオブジェクトに残されているのは、それを持っていることだけです。メモリが解放されましたか?」あまり。それは彼らが繰り返しの呼び出しから守っていたのかもしれませんDispose()
このシナリオからも保護するような方法で、しかしあなたは本当にそれに賭けることはできません(特に彼らがファイナライザーでそれを助けないので、彼らはこれまでに呼び出された最後のメソッドであり、他の種類のクリーンアップのようなそのようにフラグを立てるために再び使用されないフィールドをヌルにすることは無意味です)。参照カウントされたリソースの参照カウントを削除したり、処理するのが仕事であるアンマネージコードのコントラクトに違反したりするようなことになる可能性があります。
それで。このようなクラスのファイナライザーの最良のシナリオは、ガベージコレクションの効率を損なうことです。最悪の場合、支援しようとした完全に優れたクリーンアップコードを妨げるバグがあります。
Dispose(bool disposing)
保護されたメソッドがある場合に、MSがプロモートするために使用する(そして一部のクラスにはまだ存在する)パターンの背後にあるロジックにも注意してください。さて、このパターンについて言うべき悪いことがたくさんありますが、それを見ると、クリーンアップするものとファイナライザーでクリーンアップするものが同じではないという事実に対処するように設計されていますDispose()
-このパターンは、オブジェクトの直接保持されている非管理対象リソースが両方の場合にクリーンアップされ(質問のシナリオでは、そのようなリソースはありません)、内部保持されてIDisposable
いるオブジェクトなどの管理対象リソースは、からのみDispose()
クリーンアップされることを意味します。ファイナライザーからではありません。
管理されていないリソース、オブジェクトなど、クリーンアップする必要があるものIDisposable.Dispose()
がある場合は実装します。IDisposable
クリーンアップする必要のある管理されていないリソースが直接ある場合にのみ、ファイナライザーを作成し、そこで行う唯一のことをクリーンアップします。
ボーナスポイントについては、両方のクラスに同時に参加することは避けてください。すべてのアンマネージリソースを、そのアンマネージクラスのみを処理する使い捨てのファイナライズ可能なクラスにラップします。その機能を他のリソースと組み合わせる必要がある場合は、そのようなクラス。そうすることで、クリーンアップがより明確になり、単純になり、バグが発生しにくくなり、GCの効率へのダメージが少なくなります(あるオブジェクトのファイナライズが別のオブジェクトのファイナライズを遅らせるリスクがなくなります)。