イベントプロパティを持つクラスがあるとします。外部参照なしでローカルコンテキストでこのクラスをインスタンス化する場合、イベントにラムダ式を割り当てると、インスタンスがガベージコレクションされるのを防ぐことができますか?
{
var o = new MyClass();
o.MyClassEvent += (args) => {};
}
// Will 'o' be eligible for garbage collection here?
イベントプロパティを持つクラスがあるとします。外部参照なしでローカルコンテキストでこのクラスをインスタンス化する場合、イベントにラムダ式を割り当てると、インスタンスがガベージコレクションされるのを防ぐことができますか?
{
var o = new MyClass();
o.MyClassEvent += (args) => {};
}
// Will 'o' be eligible for garbage collection here?
いいえ、o
解放されます。ラムダ関数も解放されます。他のどこからも参照さo
れていないので、解放されるべきではない理由はありません。
短い答え、いいえ、o
解放されます。ご心配なく。
少し長い答え:
あなたのコードは多かれ少なかれ以下を行います:
o
)を参照するために、そのスレッドにローカルストレージを作成します。o
ます。o
参照があります)。この間の任意の時点で、GCはスレッドを停止し、ルート化されているオブジェクトとルート化されていないオブジェクトを調べることができます。ルートは、静的またはスレッドのローカルストレージにあるオブジェクトです(つまり、本質的に静的な形式である「スレッドローカルストレージ」ではなく、スタックによって実装された、特定のスレッドの実行におけるローカル変数を意味します)。ルート化されたオブジェクトは、ルート、それらによって参照されるオブジェクト、それらによって参照されるオブジェクトなどです。ルート化されたオブジェクトは収集されず、残りは収集されます(ファイナライザーに関係する余分なものを除いて、今のところ無視します)。
MyClassオブジェクトが作成された後のこれまでのところ、スレッドのローカルストレージによってルート化されていません。
デリゲートオブジェクトが作成された後のこれまでのところ、スレッドのローカルストレージ、またはそれへの参照を持つMyClassによってルート化されていません。
さて、次に何が起こりますか?
場合によります。ラムダはMyClassを存続させません(MyClassは存続させますが、MyClassが存続すると、ラムダも存続します)。「ここで「o」はガベージコレクションの対象になりますか?」と答えるには、これだけでは不十分です。けれど。
void Meth0()
{
var o = new MyClass();
o.MyClassEvent += (args) => {};
}//o is likely eligible for collection, though it doesn't have to be.
void Meth1()
{
int i = 0;
{
var o = new MyClass();
o.MyClassEvent += (args) => {};
}//o is likely not eligible for collection, though it could be.
while(i > 100000000);//just wasting time
}
void Meth2()
{
{
var o = new MyClass();
o.MyClassEvent += (args) => {};
int i = 0;//o is likely eligible for collection, though it doesn't have to be.
while(i > 100000000);//just wasting time
}
}
void Meth3()
{
{
var o = new MyClass();
o.MyClassEvent += (args) => {};
var x0 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
var x1 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
var x2 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
var x3 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
var x4 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
var x5 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
var x6 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
var x7 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
var x8 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
var x9 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
var x10 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
var x11 = new MyClass();//o is even more likely eligible for collection, though it doesn't have to be.
int i = 0;
while(i > 100000000);//just wasting time
}
}
Meth0は一見最も単純に見えますが、実際にはそうではないので、ここに戻ります。
Meth1では、実装はローカルストレージをそのままにしておく可能性があります。これは、ローカルストレージが不要になったためです。そのためo
、範囲内ではありませんが、実装ではそのローカルストレージが引き続き使用されます。
Meth2では、実装は使用していたのと同じローカルストレージを使用できるためo
、i
スコープ内にある場合でも、収集する資格があります(「スコープ内」とは、プログラマーがそれを使用して何かを選択できることを意味しますが、彼または彼女はそうではなく、コンパイルされたコードにはそのような概念は必要ありません)。
Meth3はストレージを再利用する可能性が高くなります。これは、Meth3を一時的に使用することで、最初からすべてのストレージを確保するのではなく、その実装がより理にかなっているためです。
これらのことのどれもこのようである必要はありません。Meth2とMeth3はどちらも、最初にメソッドに必要なすべてのストレージを確保することができます。Meth1は、再注文i
とo
割り当てに違いがないため、ストレージを再利用できます。
Meth0は、その時点でクリーンアップが行われるのではなく、呼び出し元のメソッドがローカルストレージで次に何を行うかに依存する可能性があるため、より複雑です(どちらも正当な実装です)。IIRCには、現在の実装で常にクリーンアップがありますが、よくわかりません。とにかく問題ではありません。
全体として、スコープは関係ありませんが、コンパイラーとそれ以降のJITterが、オブジェクトに関連するローカルストレージを利用できるかどうかは関係ありません。オブジェクトのメソッドとプロパティが呼び出される前にオブジェクトをクリーンアップすることも可能です(これらのメソッドとプロパティがthis
オブジェクトのフィールドまたはオブジェクトのフィールドを使用しない場合、オブジェクトが削除されていれば問題なく機能するためです!) 。
ランタイムがそのコードブロックを離れると、オブジェクトへの参照がなくなるため、ガベージコレクションが行われます。
それは、「ローカルコンテキスト」が正確に何を意味するかによって異なります。比較:
static void highMem()
{
var r = new Random();
var o = new MyClass();
o.MyClassEvent += a => { };
}
static void Main(string[] args)
{
highMem();
GC.Collect(); //yes, it was collected
}
に:
static void Main(string[] args)
{
var r = new Random();
{
var o = new MyClass();
o.MyClassEvent += a => { };
}
GC.Collect(); //no, it wasn't collected
}
私の環境(デバッグビルド、VS 2010、Win7 x64)では、最初の環境はGCの対象であり、2番目の環境は対象外でした(MyClassに200MBのメモリを使用させ、タスクマネージャーで確認した場合)。範囲外でした。これは、コンパイラがメソッドの開始時にすべてのローカル変数を宣言するためだと思います。したがってo
、C#コードで使用できなくても、CLRに対してはスコープ外ではありません。