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#オブジェクトはnew
C ++の構文を使用して作成されるオブジェクトとよく似ています-それらはヒープ上に作成されますが、C ++オブジェクトとは異なり、ランタイムによって管理されるため、破棄する必要はありません。
オブジェクトは常にヒープ上にあるため、オブジェクト参照(つまり、ポインター)がスコープから外れるという事実は問題になります。オブジェクトへの参照の存在だけでなく、オブジェクトを収集するかどうかの決定には、より多くの要因があります。
C#オブジェクト参照
Jon Skeet は、Javaのオブジェクト参照を、オブジェクトであるバルーンに接続されている文字列の断片と比較しました。同じことが、C#オブジェクト参照にも当てはまります。それらは単にオブジェクトを含むヒープの場所を指します。したがって、nullに設定しても、オブジェクトの有効期間に直接影響はありません。GCが「ポップ」するまで、バルーンは存在し続けます。
風船の類推を続けると、風船にひもが付いていない状態になると、風船が破壊される可能性があるのは当然のように思われます。実際、これは、参照カウント対象オブジェクトが非管理対象言語でどのように機能するかを正確に表しています。ただし、この方法は循環参照ではうまく機能しません。紐で接続されている2つのバルーンが、どちらのバルーンにも他の紐が付いていない場合を想像してください。単純な参照カウントルールでは、バルーングループ全体が「孤立」しているにもかかわらず、どちらも存在し続けます。
.NETオブジェクトは、屋根の下のヘリウム風船によく似ています。屋根が開いたとき(GCの実行)-一緒にテザーされているバルーンのグループがある場合でも、未使用のバルーンは浮き上がります。
.NET GCは、世代別GCとマークアンドスイープの組み合わせを使用します。ジェネレーショナルアプローチでは、未使用の可能性が高く、マークアンドスイープがランタイムにオブジェクトグラフ全体を調べ、未使用のオブジェクトグループがあるかどうかを確認するため、ランタイムが優先して最近割り当てられたオブジェクトを検査します。これは循環依存問題を適切に処理します。
また、.NET GCは別のスレッド(いわゆるファイナライザスレッド)で実行されます。これは、やることがかなりあり、メインスレッドで実行するとプログラムが中断されるためです。