先日この記事を読んでいて、なぜ Dispose メソッドと一緒にファイナライザーがあるのか疑問に思っていました。Dispose を Finalizer に追加する理由については、SOを読んでください。私の好奇心は、ファイナライザーが Dispose メソッド自体で呼び出されるのはいつですか? コード例はありますか、それともソフトウェアが実行されているシステムで起こっていることに基づいていますか? もしそうなら、GC によって Dispose メソッドが実行されないとどうなるでしょうか。
5 に答える
ここでのファイナライザーの目的は、単にメモリ リークに対する安全対策です (明示的に呼び出さない場合)。Dispose
また、プログラムのシャットダウン時にリソースを解放したい場合にオブジェクトを破棄する必要がないことも意味します。これは、GC が強制的にすべてのオブジェクトをファイナライズして収集するためです。
関連するポイントとして、ファイナライザーとは少し異なる方法でオブジェクトを配置することが重要です。
~MyClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(disposing)
{
if (!this.disposed)
{
if (disposing)
{
// Dispose managed resources here.
}
// Dispose unmanaged resources here.
}
this.disposed = true;
}
ファイナライザーで管理対象リソースを破棄したくない理由は、実際にそれらへの強力な参照を作成することになり、GC が適切にジョブを実行してそれらを収集するのを妨げる可能性があるためです。アンマネージ リソース (例: Win32 ハンドルなど) は、CLR が認識していないため、常に明示的にクローズ/破棄する必要があります。
これは主に自分自身を守るためにあります。クラスのエンド ユーザーが何をするかを指示することはできません。Dispose メソッドに加えてファイナライザーを提供することにより、ユーザーが Dispose() の呼び出しを忘れたり、クラスを誤用したりした場合でも、GC はオブジェクトを "破棄" し、リソースを適切に解放します。
Finalizer は、オブジェクトがガベージ コレクションされるときに呼び出されます。Dispose は明示的に呼び出す必要があります。次のコードでは、ファイナライザーが呼び出されますが、Dispose メソッドは呼び出されません。
class Foo : IDisposable
{
public void Dispose()
{
Console.WriteLine("Disposed");
}
~Foo()
{
Console.WriteLine("Finalized");
}
}
...
public void Go()
{
Foo foo = new Foo();
}
Dispose メソッドは、Dispose() を呼び出すか、オブジェクトを using ステートメントに含めることによって、明示的に呼び出す必要があります。GC は常にファイナライザーを呼び出すため、オブジェクトが破棄される前に何かを行う必要がある場合、ファイナライザーは少なくともオブジェクト内のすべてがクリーンアップされていることを確認する必要があります。
可能であれば、ファイナライザーでオブジェクトをクリーンアップすることは避けたいと考えています。これは、事前にオブジェクトを破棄する場合 (dispose の呼び出しなど) に比べて余分な作業が発生するためです。削除する必要があります。
まだ言及されていない重要だが微妙な注意: Dispose のほとんど考慮されていない目的は、オブジェクトが時期尚早にクリーンアップされるのを防ぐことです。ファイナライザーが予想よりも早く実行されないように、ファイナライザーを持つオブジェクトは慎重に記述する必要があります。ファイナライザーは、オブジェクトに対して行われる最後のメソッド呼び出しの開始前に実行することはできません (*)。メソッドが完了するとオブジェクトが破棄される場合は、最後のメソッド呼び出し。オブジェクトを適切に Dispose するコードは、Dispose を呼び出す前にオブジェクトを放棄できないため、適切に Dispose を使用するコードにファイナライザーが大混乱をもたらす危険はありません。一方、オブジェクトを使用する最後のメソッドが、オブジェクト参照自体を最後に使用した後にファイナライザーでクリーンアップされるエンティティを使用する場合、ガベージコレクターがオブジェクトに対して Finalize を呼び出してクリーンアップする可能性があります。まだ使用されているエンティティをアップします。解決策は、ファイナライザーによってクリーンアップされるエンティティを使用するすべての呼び出しメソッドの後に、「this」を使用するメソッド呼び出しが続く必要があることを確認することです。GC.KeepAlive(this) は、そのために使用するのに適した方法です。
(*) オブジェクトに対して何もしないインライン コードに展開される非仮想メソッドは、この規則から除外される場合がありますが、Dispose は通常、仮想メソッドであるか、仮想メソッドを呼び出します。