オブジェクトを破棄してnullに設定する必要がありますか?


310

オブジェクトを破棄してnullに設定する必要がありますか、それともガベージコレクターがスコープから外れたときにオブジェクトをクリーンアップしますか?


4
オブジェクトをnullに設定する必要がないというコンセンサスがあるようですが、Dispose()を実行する必要がありますか?
CJ7、

3
:ジェフは言ったようcodinghorror.com/blog/2009/01/...
tanathos

9
オブジェクトがIDisposableを実装する場合、私のアドバイスは常に破棄です。毎回使用ブロックを使用してください。仮定を行わないでください。偶然に任せないでください。ただし、nullに設定する必要はありません。オブジェクトがスコープから外れました。
ピーター、

11
@peter:WCFクライアントプロキシで "using"ブロックを使用しないでください
nlawalker

9
ただし、Dispose()メソッド内で一部の参照をnullに設定する必要がある場合があります。これはこの質問の微妙なバリエーションですが、重要なのは、破棄されるオブジェクトが「スコープ外」かどうかを知ることができないためです(呼び出しDispose()は保証されません)。もっとここに:stackoverflow.com/questions/6757048/...
ケビンP.ライス

回答:


239

オブジェクトは、使用されなくなったとき、およびガベージコレクターが適切であると判断したときにクリーンアップされます。オブジェクトをnullスコープ外にするためにオブジェクトをに設定する必要がある場合があります(値が不要になった静的フィールドなど)が、通常はに設定する必要はありませんnull

オブジェクトの破棄については、@ Andreに同意します。オブジェクトがある場合はIDisposableありません、それを配置することをお勧めあなたは、もはやオブジェクトがアンマネージリソースを使用する場合は特に、それを必要とするとき。アンマネージリソースを破棄しないと、メモリリークが発生します。

usingプログラムがusingステートメントのスコープを離れると、ステートメントを使用してオブジェクトを自動的に破棄できます。

using (MyIDisposableObject obj = new MyIDisposableObject())
{
    // use the object here
} // the object is disposed here

これは機能的には次と同等です。

MyIDisposableObject obj;
try
{
    obj = new MyIDisposableObject();
}
finally
{
    if (obj != null)
    {
        ((IDisposable)obj).Dispose();
    }
}

4
objが参照型であれば、finallyブロックは同等です:if (obj != null) ((IDisposable)obj).Dispose();
ランディはモニカサポート

1
@Tuzo:ありがとう!それを反映するように編集されました。
ザックジョンソン、

2
に関する一つの発言IDisposable。オブジェクトの破棄に失敗しても、通常、適切に設計されたクラスでメモリリークは発生しません。C#でアンマネージリソースを操作するときは、アンマネージリソースを解放するファイナライザーが必要です。つまり、リソースを割り当て解除する必要があるときに割り当てを解除する代わりに、ガベージコレクターが管理対象オブジェクトをファイナライズするときにそれが延期されます。ただし、それでも他の多くの問題が発生する可能性があります(解放されていないロックなど)。あなたはIDisposableしかし処分する必要があります!
アイディアカピ2015

@RandyLevyそのためのリファレンスはありますか?おかげで
基本

しかし、私の質問はDispose()がロジックを実装する必要があるということですか?何かする必要がありますか?または、内部的にDispose()が呼び出されたときに、GCにシグナルを送ります。たとえば、TextWriterのソースコードを確認したところ、Disposeには実装がありません。
Mihail Georgescu

137

C ++の場合とは異なり、C#ではオブジェクトがスコープから外れることはありません。これらは、使用されなくなったときにガベージコレクターによって自動的に処理されます。これは、変数のスコープが完全に決定的であるC ++よりも複雑なアプローチです。CLRガベージコレクターはアクティブに作成されたすべてのオブジェクトを通過し、それらが使用されている場合はうまくいきます。

オブジェクトは1つの関数で「範囲外」になる可能性がありますが、その値が返された場合、GCは呼び出し側の関数が戻り値を保持しているかどうかを調べます。

nullガベージコレクションは他のオブジェクトによって参照されているオブジェクトを特定することによって機能するため、オブジェクト参照をに設定する必要はありません。

実際には、破壊を心配する必要はありません。それは機能するだけで素晴らしいです:)

Disposeオブジェクトの操作IDisposableが終了したら、実装するすべてのオブジェクトで呼び出す必要があります。通常、次のusingようにこれらのオブジェクトでブロックを使用します。

using (var ms = new MemoryStream()) {
  //...
}

変数スコープで編集します。クレイグは、変数スコープがオブジェクトの寿命に影響を与えるかどうかを尋ねました。CLRのその側面を適切に説明するには、C ++とC#のいくつかの概念を説明する必要があります。

実際の変数スコープ

両方の言語で、変数は定義されたのと同じスコープ内でのみ使用できます-クラス、関数、または中括弧で囲まれたステートメントブロック。ただし、微妙な違いは、C#ではネストされたブロックで変数を再定義できないことです。

C ++では、これは完全に合法です。

int iVal = 8;
//iVal == 8
if (iVal == 8){
    int iVal = 5;
    //iVal == 5
}
//iVal == 8

ただし、C#ではコンパイラエラーが発生します。

int iVal = 8;
if(iVal == 8) {
    int iVal = 5; //error CS0136: A local variable named 'iVal' cannot be declared in this scope because it would give a different meaning to 'iVal', which is already used in a 'parent or current' scope to denote something else
}

これは、生成されたMSILを見ると意味があります。関数で使用されるすべての変数は、関数の開始時に定義されます。この関数を見てみましょう:

public static void Scope() {
    int iVal = 8;
    if(iVal == 8) {
        int iVal2 = 5;
    }
}

以下は生成されたILです。ifブロック内で定義されているiVal2は、実際には関数レベルで定義されていることに注意してください。事実上、これは、C#が変数と有効期間に関する限り、クラスと関数レベルのスコープしか持たないことを意味します。

.method public hidebysig static void  Scope() cil managed
{
  // Code size       19 (0x13)
  .maxstack  2
  .locals init ([0] int32 iVal,
           [1] int32 iVal2,
           [2] bool CS$4$0000)

//Function IL - omitted
} // end of method Test2::Scope

C ++のスコープとオブジェクトの有効期間

スタックに割り当てられたC ++変数がスコープから外れると、破壊されます。C ++では、スタックまたはヒープ上にオブジェクトを作成できることに注意してください。スタック上にそれらを作成すると、実行がスコープを離れると、スタックからポップされて破棄されます。

if (true) {
  MyClass stackObj; //created on the stack
  MyClass heapObj = new MyClass(); //created on the heap
  obj.doSomething();
} //<-- stackObj is destroyed
//heapObj still lives

C ++オブジェクトがヒープ上に作成された場合、それらは明示的に破棄する必要があります。それ以外の場合はメモリリークです。スタック変数ではそのような問題はありません。

C#オブジェクトの有効期間

CLRでは、オブジェクト(つまり、参照型)は常にマネージヒープ上に作成されます。これは、オブジェクト作成構文によってさらに強化されます。このコードスニペットを考えてみましょう。

MyClass stackObj;

C ++では、これMyClassによりスタック上にインスタンスが作成され、デフォルトのコンストラクターが呼び出されます。C#ではMyClass、何も指さないクラスへの参照が作成されます。クラスのインスタンスを作成する唯一の方法は、new演算子を使用することです。

MyClass stackObj = new MyClass();

ある意味で、C#オブジェクトはnewC ++の構文を使用して作成されるオブジェクトとよく似ています-それらはヒープ上に作成されますが、C ++オブジェクトとは異なり、ランタイムによって管理されるため、破棄する必要はありません。

オブジェクトは常にヒープ上にあるため、オブジェクト参照(つまり、ポインター)がスコープから外れるという事実は問題になります。オブジェクトへの参照の存在だけでなく、オブジェクトを収集するかどうかの決定には、より多くの要因があります。

C#オブジェクト参照

Jon Skeet は、Javaのオブジェクト参照を、オブジェクトであるバルーンに接続されている文字列の断片と比較しました。同じことが、C#オブジェクト参照にも当てはまります。それらは単にオブジェクトを含むヒープの場所を指します。したがって、nullに設定しても、オブジェクトの有効期間に直接影響はありません。GCが「ポップ」するまで、バルーンは存在し続けます。

風船の類推を続けると、風船にひもが付いていない状態になると、風船が破壊される可能性があるのは当然のように思われます。実際、これは、参照カウント対象オブジェクトが非管理対象言語でどのように機能するかを正確に表しています。ただし、この方法は循環参照ではうまく機能しません。紐で接続されている2つのバルーンが、どちらのバルーンにも他の紐が付いていない場合を想像してください。単純な参照カウントルールでは、バルーングループ全体が「孤立」しているにもかかわらず、どちらも存在し続けます。

.NETオブジェクトは、屋根の下のヘリウム風船によく似ています。屋根が開いたとき(GCの実行)-一緒にテザーされているバルーンのグループがある場合でも、未使用のバルーンは浮き上がります。

.NET GCは、世代別GCとマークアンドスイープの組み合わせを使用します。ジェネレーショナルアプローチでは、未使用の可能性が高く、マークアンドスイープがランタイムにオブジェクトグラフ全体を調べ、未使用のオブジェクトグループがあるかどうかを確認するため、ランタイムが優先して最近割り当てられたオブジェクトを検査します。これは循環依存問題を適切に処理します。

また、.NET GCは別のスレッド(いわゆるファイナライザスレッド)で実行されます。これは、やることがかなりあり、メインスレッドで実行するとプログラムが中断されるためです。


1
@Igor:「範囲外」になると、オブジェクト参照がコンテキスト外になり、現在のスコープで参照できないことを意味します。確かに、これはまだC#で​​発生します。
CJ7 2010年

@Craig Johnston、ランタイムによって決定される変数の有効期間とコンパイラーによって使用される変数のスコープを混同しないでください-それらは異なります。ローカル変数はまだスコープ内にありますが、「ライブ」ではない場合があります。
ランディは

1
@Craig Johnston:blogs.msdn.com/b/ericgu/archive/2004/07/23/192842.aspxを参照してください。ランタイムは、コードを自由に分析し、特定のポイントを超えて変数の使用法がないことを判断します。したがって、その変数はそのポイントを超えて存続しません(つまり、目的のルートとして扱いません) GCの)。」
ランディは

1
@Tuzo:本当です。それがGC.KeepAliveの目的です。
Steven Sudit

1
@クレイグジョンストン:いいえ、はい。.NETランタイムがそれを管理し、適切に機能するためです。はい、プログラマの仕事は(ただ)コンパイルするコードを書くことではなく、実行するコードを書くことです。場合によっては、ランタイムが内部で何をしているかを知るのに役立ちます(トラブルシューティングなど)。優れたプログラマーと優れたプログラマーを区別するのに役立つのは一種の知識だと主張する人もいるでしょう。
ランディは

18

他の人が言ったDisposeように、クラスが実装する場合は必ず呼び出す必要がありますIDisposable。私はこれに関してかなり固い立場をとっています。たとえば、を呼び出すことDisposeDataSet、それを分解し、意味のあることを何も行わなかったことがわかったため、無意味であると主張する人もいます。しかし、私はその議論には多くの誤りがあると思います。

この件に関して尊敬される個人による興味深い議論のためにこれを読んでください。次に、ジェフリー・リヒターが間違った陣営にいると思う理由をここで私の推論を読んでください。

次に、への参照を設定する必要があるかどうかについて説明しnullます。答えはいいえだ。次のコードで私のポイントを説明しましょう。

public static void Main()
{
  Object a = new Object();
  Console.WriteLine("object created");
  DoSomething(a);
  Console.WriteLine("object used");
  a = null;
  Console.WriteLine("reference set to null");
}

では、によって参照されるオブジェクトaはいつ収集に適していると思いますか?a = nullあなたが電話の後に言ったなら、あなたは間違っています。Mainメソッドが完了した後にあなたが言ったなら、あなたも間違っています。正解は、への通話にいつでも収集の対象になるということですDoSomething。そうです。これは、資格がある前に、参照に設定されてnullも、呼び出しの前に、おそらくとDoSomething完了します。これは、JITコンパイラーが、まだルート化されている場合でも、オブジェクト参照が逆参照されなくなったことを認識できるためです。


3
aクラスのプライベートメンバーフィールドの場合はどうなりますか?anullに設定されていない場合、GCはaいくつかのメソッドで再び使用されるかどうかを知る方法がありませんよね?したがってa、含まれているクラス全体が収集されるまで収集されません。番号?
ケビンP.ライス

4
@ケビン:正解。場合はa、クラスのメンバーであり、クラスが含有aまだ根付いたと使用中のそれはあまりにも周りにハングアップしました。これは、それをに設定するnullことが有益なシナリオの1つです。
ブライアンギデオン

1
あなたの論点Disposeは、重要である理由と結びついDisposeています。オブジェクトへのルート化された参照がないと、オブジェクトに対して(または他のインライン化できないメソッドを)呼び出すことはできません。Disposeオブジェクトを使用して実行された後に呼び出すことで、ルート化された参照が、最後に実行されたアクションの間ずっと存在し続けることが保証されます。呼び出しなしでオブジェクトへのすべての参照を放棄すると、Dispose皮肉なことに、オブジェクトのリソースの解放が早すぎる場合があります。
スーパーキャット、2015年

この例と説明は、参照をnullに設定しないという強い提案では決定的なものではないようです。つまり、Kevinのコメントを除いて、破棄されたにnull 設定された参照は非常に害のないものに見えるので、害は何ですか?何か不足していますか?
ダトンプソン

13

C#でオブジェクトをnullに設定する必要はありません。コンパイラとランタイムは、スコープから外れた場合に対処します。

はい、IDisposableを実装するオブジェクトを破棄する必要があります。


2
ラージオブジェクトへの長期間(または静的)の参照があるwant場合は、使い終わったらすぐに無効にして、自由に再利用できるようにします。
Steven Sudit

12
あなたが「それでやり遂げた」なら、それは静的であってはなりません。静的ではなく、「長期間有効」である場合は、使い終わった後もすぐにスコープから外れるはずです。参照をnullに設定する必要がある場合は、コードの構造に問題があることを示しています。
EMP

あなたはあなたがやり遂げる静的なアイテムを持つことができます。考えてみましょう。ユーザーフレンドリーな形式でディスクから読み取られ、プログラムの使用に適した形式に解析される静的リソース。最終的に、それ以上の目的を果たさない生データのプライベートコピーが作成される可能性があります。(実世界の例:解析は2パスルーチンであるため、読み取ったデータを単純に処理することはできません。)
Loren Pechtel

1
一時的にのみ使用される場合は、静的フィールドに生データを格納しないでください。もちろん、それ行うことができます。これは、まさにこの理由のために良い習慣ではありません。その場合、その寿命を手動で管理する必要があります。
EMP

2
それを処理するメソッドのローカル変数に生データを格納することでそれを回避します。メソッドは保持する処理済みデータを返しますが、メソッドが終了すると、生データのローカルがスコープ外になり、自動的にGCされます。
EMP

11

私はここで一般的な答えに同意します。はい、破棄する必要があり、通常は変数をnullに設定するべきではありません... はい、それはメモリ管理に役立ちます(場合によっては役立ちます)が、その主な目的は、希少なリソースを確定的に解放することです。

たとえば、ハードウェアポート(シリアルなど)、TCP / IPソケット、ファイル(排他アクセスモード)、またはデータベース接続を開くと、解放されるまで他のコードがこれらのアイテムを使用できなくなりました。Disposeは通常、これらのアイテムを解放します(GDIやその他の「os」ハンドルなど、1000種類も利用可能ですが、全体的にまだ制限されています)。所有者オブジェクトでdiposeを呼び出さず、これらのリソースを明示的に解放しない場合は、今後同じリソースをもう一度開こうとすると(または別のプログラムがそうします)、未処理の収集されていないオブジェクトにアイテムが開いているため、開くことができません。もちろん、GCがアイテムを収集すると(Disposeパターンが正しく実装されている場合)、リソースは解放されます...しかし、いつ解放されるかはわからないため、そのリソースを再び開くのが安全である時期を知る。これは、Disposeが回避する主要な問題です。もちろん、これらのハンドルを解放するとメモリも解放されることが多く、決して解放しないとメモリが解放されない可能性があります。したがって、メモリリーク、またはメモリクリーンアップの遅延に関するすべての話です。

私はこれが問題を引き起こす実際の例を見てきました。たとえば、SQLサーバーの「接続プールがいっぱいになっている」ため、最終的にはデータベースに接続できない(短時間、またはWebサーバープロセスが再起動されるまで)ASP.Net Webアプリケーションを見てきました...すなわち、非常に多くの接続が作成され、短期間で明示的に解放されなかったため、新しい接続を作成できず、プール内の接続の多くはアクティブではありませんが、未分割および未収集のオブジェクトによって参照されているため、 '再利用できません。必要に応じてデータベース接続を正しく破棄することで、この問題が発生しないことを保証します(少なくとも非常に高い同時アクセスがない限り)。


11

オブジェクトがを実装している場合はIDisposable、はい、破棄する必要があります。オブジェクトはネイティブリソース(ファイルハンドル、OSオブジェクト)にぶら下がっている可能性があり、そうでなければすぐには解放されない可能性があります。これにより、リソースの不足、ファイルロックの問題、およびその他の方法では回避できるような微妙なバグが発生する可能性があります。

MSDNのDisposeメソッドの実装も参照してください。


しかし、ガベージコレクターはDispose()を呼び出しませんか?もしそうなら、なぜあなたはそれを呼ぶ必要があるのですか?
CJ7 2010年

明示的に呼び出さない限り、呼び出される保証はありませんDispose。また、オブジェクトが希少なリソースを保持している場合や、一部のリソース(ファイルなど)をロックしている場合は、できるだけ早く解放する必要があります。GCがそれを行うのを待つのは次善の策です。
Chris Schmich

12
GCがDispose()を呼び出すことはありません。GCは、規則によりリソースをクリーンアップするファイナライザを呼び出す場合があります。
adrianm 2010年

@adrianm:might電話ではなくwill電話。
レッピー

2
@leppie:ファイナライザは確定的ではなく、呼び出されない可能性があります(たとえば、appdomainがアンロードされている場合)。確定的なファイナライズが必要な場合は、クリティカルハンドラーと呼ばれるものを実装する必要があります。CLRは、これらのオブジェクトを特別に処理して、確実にファイナライズされるようにします(たとえば、
ローメモリ

9

IDisposableインターフェイスを実装している場合は、破棄する必要があります。残りはガベージコレクターが処理します。

編集:using使い捨てアイテムを操作するときにコマンドを使用するのが最善です:

using(var con = new SqlConnection("..")){ ...

5

オブジェクトが実装されたら、IDisposable呼び出す必要がありますDispose(またはClose、場合によっては、Disposeが呼び出されます)。

通常null、オブジェクトをに設定する必要はありません。GCはオブジェクトが使用されなくなることを認識するからです。

オブジェクトをに設定した場合、1つの例外がありますnull。作業する必要のある多くのオブジェクトを(データベースから)取得し、コレクション(または配列)に格納します。「作業」が完了したらnull、GCが作業の完了を認識していないため、オブジェクトをに設定します。

例:

using (var db = GetDatabase()) {
    // Retrieves array of keys
    var keys = db.GetRecords(mySelection); 

    for(int i = 0; i < keys.Length; i++) {
       var record = db.GetRecord(keys[i]);
       record.DoWork();
       keys[i] = null; // GC can dispose of key now
       // The record had gone out of scope automatically, 
       // and does not need any special treatment
    }
} // end using => db.Dispose is called

4

通常、フィールドをnullに設定する必要はありません。ただし、管理されていないリソースは常に破棄することをお勧めします。

経験から、次のことを行うこともお勧めします。

  • イベントが不要になった場合は、サブスクライブを解除してください。
  • デリゲートまたは式を保持しているフィールドが不要になった場合は、nullに設定します。

上記のアドバイスに従わなかった直接的な結果である問題を見つけるのが非常に難しいことに遭遇しました。

これを行うのに適した場所はDispose()ですが、通常は早いほど良いでしょう。

一般に、オブジェクトへの参照が存在する場合、ガベージコレクタ(GC)は、オブジェクトがもう使用されていないことを理解するのに数世代かかることがあります。その間、オブジェクトはメモリに残ります。

アプリが予想よりもはるかに多くのメモリを使用していることがわかるまで、これは問題にはなりません。その場合は、メモリプロファイラーを接続して、クリーンアップされていないオブジェクトを確認します。他のオブジェクトを参照するフィールドをnullに設定し、破棄時にコレクションを消去すると、GCがメモリから削除できるオブジェクトを把握するのに役立ちます。GCは使用されたメモリをより速く回収し、アプリのメモリ消費量を大幅に減らし、高速化します。


1
「イベントとデリゲート」についてどういう意味ですか?これらで「クリーンアップ」する必要があるものは何ですか?
CJ7 2010年

@クレイグ-私は私の答えを編集しました。うまくいけば、これで少しわかりやすくなります。
Marnix van Valen、

3

常にdisposeを呼び出します。リスクを冒す価値はありません。大規模な管理エンタープライズアプリケーションは、慎重に扱う必要があります。仮定を行うことはできません。さもなければ、それはあなたを噛むために戻ってきます。

レッピーを聞かないでください。

多くのオブジェクトは実際にはIDisposableを実装していないので、それらについて心配する必要はありません。それらが本当に範囲外になると、自動的に解放されます。また、何かをnullに設定しなければならない状況に遭遇したこともありません。

起こり得ることの1つは、多くのオブジェクトを開いたままにできることです。これにより、アプリケーションのメモリ使用量が大幅に増える可能性があります。これが実際にメモリリークなのか、それともアプリケーションが多くのことを実行しているだけなのかを判断するのが難しい場合があります。

メモリプロファイルツールはそのようなことに役立ちますが、注意が必要な場合があります。

また、必要のないイベントは必ず登録解除してください。また、WPFバインディングとコントロールにも注意してください。通常の状況ではありませんが、基になるオブジェクトにバインドされているWPFコントロールがある状況に遭遇しました。基になるオブジェクトは大きく、大量のメモリを消費しました。WPFコントロールは新しいインスタンスに置き換えられていましたが、古いインスタンスはなんらかの理由でまだハングしていました。これにより、大きなメモリリークが発生しました。

後のサイトではコードの記述は不十分ですが、重要なのは、使用されていないものは範囲外であることを確認したいということです。メモリ内の何が有効で何がそこにあるべきでないかを知るのは難しいので、メモリプロファイラで見つけるのに長い時間がかかりました。


2

私も答えなければなりません。JITは、変数の使用状況の静的分析からコードとともにテーブルを生成します。これらのテーブルエントリは、現在のスタックフレームの「GCルート」です。命令ポインタが進むと、それらのテーブルエントリは無効になり、ガベージコレクションの準備が整います。したがって、スコープ付き変数の場合は、nullに設定する必要はありません。GCがオブジェクトを収集します。メンバーまたは静的変数の場合は、nullに設定する必要があります

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.