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


108

私はCLRとGCの動作に魅了されています(C#を介してCLRを読んだり、ジョンスキートの本/投稿などを読んだりして、知識の拡大に取り組んでいます)。

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

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

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

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

using (MyDisposableObj mydispobj = new MyDisposableObj())
{

}

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

回答:


210

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

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

usingステートメントを記述する場合、それはtry / finallyブロックの構文上の糖衣でDisposeあり、usingステートメントの本文のコードが例外をスローした場合でも呼び出されます。これ、オブジェクトがブロックの最後でガベージコレクションされることを意味するものではありません

破棄は、アンマネージリソース(非メモリリソース)に関するものです。これらは、UIハンドル、ネットワーク接続、ファイルハンドルなどです。これらは限られたリソースであるため、通常はできるだけ早く解放する必要があります。あなたは、実装する必要がありますIDisposableあなたのタイプは、管理対象外のリソースを「所有」するたびに(通常経由で直接いずれかで、IntPtr(例えばビア間接的に)またはStream、A SqlConnectionなど)。

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

ひねりはファイナライズです。ガベージコレクターは、到達できなくなったがファイナライザーを備えたオブジェクトのリストを保持します(~Foo()C#のように書かれていますが、混乱を招きます-それらはC ++のデストラクタには似ていません)。メモリが解放される前に余分なクリーンアップを行う必要がある場合に備えて、これらのオブジェクトに対してファイナライザを実行します。

ファイナライザは、ほとんどの場合、そのタイプのユーザーが通常の方法で破棄するのを忘れた場合に、リソースをクリーンアップするために使用されます。そのため、を開くFileStreamDisposeorの呼び出しを忘れた場合Close、ファイナライザは最終的に基になるファイルハンドルを解放します。よく書かれたプログラムでは、ファイナライザはほとんど私の意見では発動しないはずです。

変数を null

変数を設定する際の1つの小さなポイントnull-これは、ガベージコレクションのためにほとんど必要ありません。それがメンバー変数である場合、それを実行したい場合がありますが、私の経験では、オブジェクトの「一部」が不要になることはまれです。ローカル変数の場合、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

それは一度ローカル変数を設定する価値があるが、nullあなたがループにいるときで、変数を使用するループの必要性のいくつかの支店がありますが、あなたがそうでない時点に達した知っています。例えば:

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 / finalizerの実装

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

Joe Duffyには、ファイナライザとIDisposable(多くのスマートフォークで共同作成)に関する非常に長い一連のガイドラインがあり、読む価値があります。クラスをシールすると、人生が非常に簡単になることに注意してください。Dispose新しい仮想Dispose(bool)メソッドなどを呼び出すためのオーバーライドのパターンは、クラスが継承用に設計されている場合にのみ関係します。

これは少しばかげていますが、どこに行きたいかを明確に尋ねてください:)


「ローカル変数をnullに設定する価値がある可能性がある1回」-おそらく、厄介な「キャプチャー」シナリオ(同じ変数の複数のキャプチャー)のいくつかも-投稿を複雑にする価値はないかもしれません!+1 ...
マークグラベル

@マーク:それは本当です-取得した変数についてさえ考えていませんでした。うーん。ええ、私はそれをそのままにするつもりだと思います;)
ジョン・スキート

上記のコードスニペットで「foo = null」を設定するとどうなるか教えていただけますか?私の知る限り、この行はマネージヒープのfooオブジェクトを指す変数の値のみをクリアしますか?そこで問題は、そこでfooオブジェクトはどうなるのでしょうか。それを処分と呼ぶべきではないでしょうか?
odiseh 2010

@odiseh:オブジェクトが使い捨ての場合は、はい-処分する必要があります。回答のそのセクションでは、ガベージコレクションのみを扱いましたが、これは完全に別のものです。
Jon Skeet、2010

1
私はいくつかのIDisposableの懸念についての明確化を探していたので、「IDisposable Skeet」を探してこれを見つけました。すごい!:D
Maciej Wozniak

22

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

myclass = null;

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


7
それはあり、まだその行を実行した後は存在しません-それは、ガベージコレクトされている可能性があり前に、その行。JITはスマートです。このような行を作成すると、ほとんどの場合、無関係になります。
Jon Skeet、

6
nullに設定すると、オブジェクトによって保持されているリソースが解放されることはありません。GCは破棄せず、ファイナライズするだけなので、オブジェクトがアンマネージリソースを直接保持していて、ファイナライザが破棄しない(またはファイナライザがない)場合、それらのリソースはリークします。知っておくべきこと。
LukeH、2009

6

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

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

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

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

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


この答えは、「再帰的」の答えを補完するものか、詳細なものだと思います。
dance2die 2009
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.