web-dev-qa-db-ja.com

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

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

284
CJ7

オブジェクトは、使用されなくなったとき、およびガベージコレクタが適切であると判断したときにクリーンアップされます。場合によっては、オブジェクトを範囲外にするためにオブジェクトを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();
    }
}
222
Zach Johnson

オブジェクトは、C++の場合のようにC#では対象外になりません。それらはもう使用されなくなったときにガベージコレクタによって自動的に処理されます。これは、変数の範囲が完全に確定的であるC++よりも複雑な方法です。 CLRガベージコレクタは、作成されたすべてのオブジェクトを積極的に調べ、それらが使用されている場合は解決します。

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

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

実際には、あなたは破壊について心配する必要はありません、それはただうまくいく、そしてそれは素晴らしいです:)

Disposeは、作業が終了したら、IDisposableを実装するすべてのオブジェクトに対して呼び出す必要があります。通常は、usingブロックをこれらのオブジェクトと一緒に使用します。

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

EDIT変数スコープ。 Craigは、変数の有効範囲がオブジェクトの有効期間に何らかの影響を与えるかどうかを尋ねました。 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#オブジェクトは、C++のnew構文を使用して作成されたオブジェクトによく似ています - それらはヒープ上で作成されますが、C++オブジェクトとは異なりランタイムによって管理されるので、破壊する心配はありません。

オブジェクトはヒープ上で常になので、オブジェクト参照(つまりポインタ)が範囲外になるという事実は意味がありません。オブジェクトを収集するかどうかを決定する際には、単にそのオブジェクトへの参照が存在するだけでなく、さらに多くの要因が関係します。

C#オブジェクト参照

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

風船のたとえを続けると、風船に紐が付いていなくなると、風船が破壊される可能性があることは論理的に思えるでしょう。実際、これは参照カウントオブジェクトが管理されていない言語でどのように機能するかということです。この方法を除いて、循環参照にはあまりうまくいきません。 2本の風船が紐でつながっているが、どちらの風船にも紐が付いていないと想像してください。単純な参照カウント規則の下では、バルーングループ全体が「孤立」していても、両方とも存在し続けます。

.NETオブジェクトは、屋根の下のヘリウム風船によく似ています。屋根が開いたとき(GCが走ったとき) - 一緒につながれている風船のグループがあるかもしれないのに、未使用の風船は離れて浮かびます。

.NET GCは、世代別GCとマークアンドスイープの組み合わせを使用しています。世代別アプローチでは、未使用の可能性が高いため、最も最近割り当てられたオブジェクトを検査することを優先するランタイムが含まれます。これは循環依存問題を適切に処理します。

また、.NET GCは別のスレッド(いわゆるファイナライザスレッド)上でも実行できますが、メインスレッドで実行するとプログラムに割り込む可能性があります。

129
Igor Zevaka

他の人が言っているように、クラスがDisposeを実装している場合は、必ずIDisposableを呼び出したいと思います。私はこれについてかなり厳格な立場をとります。たとえば、Disposeに対してDataSetを呼び出すことは意味がないので、逆アセンブルしたために無意味だと主張する人もいるかもしれません。しかし、その議論にはたくさんの誤りがあると思います。

主題についての尊敬される個人による興味深い議論のために this を読んでください。それなら私の推論 here を読んでください。なぜ私はJeffery Richterが間違った野営地にいるのだと思います。

それでは、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メソッドが完了した後にあなたが言ったなら、あなたも間違っています。正しい答えは、いつかduring _ DoSomethingへの呼び出しに適しているということです。そうです。 beforeに適格です。参照はnullに設定され、おそらくDoSomethingの呼び出しが完了する前でも可能です。これは、JITコンパイラーが、オブジェクト参照がまだ根付いている場合でも、オブジェクト参照が参照解除されなくなったことを認識できるためです。

17
Brian Gideon

C#では、オブジェクトをnullに設定する必要はありません。コンパイラとランタイムは、それらがもはや有効範囲内になくなったときを理解するようにします。

はい、IDisposableを実装しているオブジェクトは破棄してください。

13
EMP

私はここでの一般的な答えに賛成です。はい、あなたは処分すべきであり、あなたは一般的に変数をnullに設定すべきではありません...しかし私は処分は主にメモリ管理に関するものではないと指摘したいと思いました。はい、それはメモリ管理に役立ちます(そして時々します)が、それは主な目的はあなたに乏しいリソースの決定論的解放を与えることです。

たとえば、ハードウェアポート(シリアルなど)、TCP/IPソケット、ファイル(排他アクセスモード)、さらにはデータベース接続を開いた場合、それらが解放されるまで他のコードがそれらの項目を使用できないようになりました。 Disposeは一般的にこれらのアイテムを(GDIや他の "os"ハンドルなどと共に利用可能にしますが、それらは1000個以上ありますが、それでも全体的には限られています)。所有者オブジェクトに対してdiposeを呼び出さずに明示的にこれらのリソースを解放する場合は、将来的に同じリソースを再度開くことを試みてください(または別のプログラムが行います)。 。もちろん、GCがアイテムを収集すると(Disposeパターンが正しく実装されていれば)、リソースは解放されます...しかし、いつ解放されるのかはわかりません。そのリソースを開きます。これが、Disposeの主な問題です。もちろん、これらのハンドルを解放するとメモリも解放されることが多く、解放しないとそのメモリが解放されることはありません。したがって、メモリリークやメモリクリーンアップの遅延に関するすべての話があります。

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

11
Yort

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

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

11
Chris Schmich

それらがIDisposableインターフェイスを実装しているなら、あなたはそれらを処分するべきです。ガベージコレクタが残りの面倒を見るでしょう。

編集:使い捨て可能なアイテムを扱うときは、usingコマンドを使用するのが最善です。

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

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

経験から、私はあなたに次のことをするように勧めます。

  • 不要になったイベントの購読を中止します。
  • デリゲートまたは式を保持しているフィールドが不要になった場合は、それをnullに設定します。

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

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

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

アプリが予想以上に多くのメモリを使用していることが判明するまで、それは問題にならないかもしれません。それが起こったら、どんなオブジェクトがクリーンアップされていないかを見るためにメモリプロファイラーを接続してください。他のオブジェクトを参照するフィールドをnullに設定し、コレクションを破棄して破棄すると、GCはメモリからどのオブジェクトを削除できるかを実際に把握するのに役立ちます。 GCは、使用済みメモリをより早く再利用して、アプリケーションのメモリ使用量を大幅に減らし、高速にします。

4

常に処分を呼び出します。リスクに見合う価値はありません。大規模なマネージドエンタープライズアプリケーションは敬意を持って扱われるべきです。仮定することはできませんか、そうでなければそれはあなたをかむために戻ってきます。

レッピーに耳を傾けるな。

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

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

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

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

後部では、コードの記述が不完全でしたが、重要なのは、使用されていないものが範囲外になることを確認することです。メモリプロファイラを使って検索するには長い時間がかかりました。メモリ内のどの要素が有効で、何があってはいけないのかがわかりにくいからです。

3
peter

オブジェクトがIDisposableを実装するとき、あなたはDisposeを呼び出すべきです(あるいはClose、場合によってはDisposeを呼び出すでしょう)。

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

オブジェクトをnullに設定すると例外が1つあります。作業する必要がある多数のオブジェクトを(データベースから)取り出して、それらをコレクション(または配列)に保管するとき。 "作業"が完了したら、オブジェクトを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
3
GvS

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

2
Hui

.NET Rocksのこのエピソードでは、この問題について(Disposeパターンの背後にある歴史と共に)良い議論があります。

http://www.dotnetrocks.com/default.aspx?showNum=10

0
Rob Windsor