web-dev-qa-db-ja.com

オブジェクトをnullとDispose()に設定する

私はCLRとGCの仕組みに魅了されています(C#やJon Skeetの本/投稿などを介してCLRを読むことで、これに関する知識の拡大に取り組んでいます)。

とにかく、言うことの違いは何ですか:

MyClass myclass = new MyClass();
myclass = null;

または、MyClassにIDisposableとデストラクタを実装させ、Dispose()を呼び出すことで?

また、usingステートメント(たとえば、以下)を含むコードブロックがある場合、コードをステップ実行してusingブロックを終了すると、オブジェクトは破棄されますか、またはガベージコレクションが発生したときですか? usingブロックでDispose()を呼び出すとどうなりますか?

using (MyDisposableObj mydispobj = new MyDisposableObj())
{

}

ストリームクラス(BinaryWriterなど)にはFinalizeメソッドがありますか?なぜそれを使用したいのですか?

106
GurdeepS

廃棄物をガベージコレクションから分離することが重要です。それらは完全に別のものであり、共通点が1つありますが、これについては後で説明します。

Dispose、ガベージコレクションおよびファイナライズ

usingステートメントを記述するとき、それはtry/finallyブロックの単純なシュガーであるため、Disposeステートメントの本文のコードが例外をスローしても、usingが呼び出されます。 does n'tは、ブロックの終わりにオブジェクトがガベージコレクションされることを意味します。

廃棄は管理されていないリソース(非メモリリソース)です。これらはUIハンドル、ネットワーク接続、ファイルハンドルなどです。これらは限られたリソースであるため、通常はできるだけ早くリリースする必要があります。タイプがアンマネージリソースを「所有」する場合は常に、直接(通常はIDisposableを介して)または間接的に(たとえばIntPtrStreamなどを介して)SqlConnectionを実装する必要があります。

ガベージコレクション自体はメモリに関するものです-ちょっとした工夫が必要です。ガベージコレクターは、参照できなくなったオブジェクトを見つけて解放できます。ただし、常にガベージを検索するわけではありません-必要なことを検出した場合のみです(たとえば、ヒープの1つの「世代」がメモリ不足になった場合)。

ねじれはfinalizationです。ガベージコレクターは、到達不能になったがファイナライザー(C#では~Foo()として記述された、やや紛らわしい-C++デストラクタのようなものではない)のオブジェクトのリストを保持します。メモリが解放される前に追加のクリーンアップを行う必要がある場合に備えて、これらのオブジェクトでファイナライザを実行します。

ファイナライザは、ほとんどの場合、タイプのユーザーがリソースを整理するのを忘れた場合にリソースをクリーンアップするために使用されます。したがって、FileStreamを開いたがDisposeまたはCloseの呼び出しを忘れると、ファイナライザは[最終的に基になるファイルハンドルを解放します。よく書かれたプログラムでは、ファイナライザーはほとんど私の意見では発動すべきではありません。

変数をnullに設定する

変数をnullに設定する際の1つの小さな点-これは、ガベージコレクションのためにほとんど必要ありません。私の経験では、オブジェクトの「一部」が不要になることはめったにありませんが、それがメンバー変数である場合、あなたは時々それをしたいかもしれません。ローカル変数の場合、JITは通常、(リリースモードで)十分にスマートであるため、いつ参照を使用しないのかを知ることができます。例えば:

_StringBuilder sb = new StringBuilder();
sb.Append("Foo");
string x = sb.ToString();

// The string and StringBuilder are already eligible
// for garbage collection here!
int y = 10;
DoSomething(y);

// These aren't helping at all!
x = null;
sb = null;

// Assume that x and sb aren't used here
_

mayがローカル変数をnullに設定する価値があるのは、ループに入っているときであり、ループの一部の分岐では変数を使用する必要がありますが、 veはあなたがそうしないポイントに達しました。例えば:

_SomeObject foo = new SomeObject();

for (int i=0; i < 100000; i++)
{
    if (i == 5)
    {
        foo.DoSomething();
        // We're not going to need it again, but the JIT
        // wouldn't spot that
        foo = null;
    }
    else
    {
        // Some other code 
    }
}
_

IDisposable/finalizersの実装

それでは、独自の型でファイナライザを実装する必要がありますか?ほぼ間違いない。 indirectlyアンマネージリソースのみを保持する場合(たとえば、メンバー変数としてFileStreamを持っている場合)、独自のファイナライザーを追加しても効果はありません。ストリームはほとんど確実にガベージの対象になりますオブジェクトが存在するときにコレクションを取得します。したがって、FileStreamがファイナライザーを持っていることだけに依存することができます(必要に応じて、他のものを参照するなど)。管理されていないリソースを「ほぼ」直接保持したい場合、 SafeHandle があなたの友人です-始めるには少し時間がかかりますが、それはあなたが ほとんどファイナライザを再度書く必要はありません 。通常、ファイナライザーが必要なのは、リソース(IntPtr)に実際に直接ハンドルがある場合のみで、できるだけ早くSafeHandleに移動する必要があります。 (そこには2つのリンクがあります-理想的には両方を読んでください。)

Joe Duffyには ファイナライザとIDisposableに関する非常に長いガイドラインのセット (多くの賢い人々と共同で書いた)があり、読む価値があります。クラスを封印すると、生活がずっと楽になることを知っておく価値があります:Disposeをオーバーライドして新しい仮想Dispose(bool)メソッドなどを呼び出すパターンは、クラスが設計されている場合にのみ関連します継承。

これはちょっとしたとりあえずされていますが、どこに行きたいのか明確にしてください:)

205
Jon Skeet

オブジェクトを破棄すると、リソースは解放されます。変数にnullを割り当てると、参照が変更されるだけです。

myclass = null;

これを実行した後、myclassが参照していたオブジェクトはまだ存在し、GCがそれをクリーンアップするまで継続します。 Disposeが明示的に呼び出された場合、またはそれがusingブロック内にある場合、すべてのリソースはできるだけ早く解放されます。

22
recursive

2つの操作は、互いにあまり関係ありません。参照をnullに設定すると、単純にそれが実行されます。それ自体は、参照されたクラスにはまったく影響しません。変数は、それが使用したオブジェクトを指すだけではなく、オブジェクト自体は変更されていません。

Dispose()を呼び出すときは、オブジェクト自体に対するメソッド呼び出しです。 Disposeメソッドが何をするにしても、オブジェクトに対して実行されます。ただし、これはオブジェクトへの参照には影響しません。

重複する唯一の領域は、whenオブジェクトへの参照がなくなった場合です。最終的にガベージコレクションを取得します。また、クラスがIDisposableインターフェイスを実装している場合、オブジェクトがガベージコレクションされる前に、オブジェクトに対してDispose()が呼び出されます。

ただし、次の2つの理由により、参照をnullに設定した直後には発生しません。最初に、他の参照が存在する可能性があるため、まだガベージコレクションはまったく行われません。2番目に、それが最後の参照であっても、ガベージコレクションの準備ができている場合、ガベージコレクタが削除するまで何も起こりません。オブジェクト。

オブジェクトでDispose()を呼び出しても、オブジェクトは「キル」されません。通常はオブジェクトをクリーンアップするために使用されますcanは後で安全に削除されますが、最終的にはDisposeには魔法のようなものはなく、単なるクラスメソッドです。

6
jalf