Say you have a class with an event property. If you instantiate this class in a local context, without outside references, would assigning a lambda expression to the event prevent the instance from becoming garbage collected?
{
var o = new MyClass();
o.MyClassEvent += (args) => {};
}
// Will 'o' be eligible for garbage collection here?
No, o
will get freed, and so will the lambda function. There are no references to o
from anywhere else, so there is no reason that it should not get freed.
Short answer, no, o
will be freed. Don't worry about it.
Slightly longer answer:
Your code does more or less the following:
- Create some local storage on that thread for a reference to the new MyClass (
o
).
- Create the new MyClass
- Store a reference to the new MyClass at
o
.
- Create a new delegate from the lambda.
- Assign that delegate to the event (
o
now has a reference to the delegate).
At any point during this, the GC may stop the threads, and then examine which objects are or are not rooted. Roots are objects that are static or in a thread's local storage (by which I mean the local variables in a given thread's execution, implemented by a stack, rather than "Thread-Local Storage" which is essentially a form of static). Rooted objects are roots, objects referenced by them, objects referenced by those, and so on. Rooted objects will not be collected, the rest will (barring some extra stuff to do with finalisers we'll ignore for now).
At no point so far after the MyClass object being created has it not been rooted by the thread's local storage.
At no point so far after the delegate object being created has it not either been rooted by the thread's local storage, or by the MyClass having a reference to it.
Now, what happens next?
It depends. The lambda won't keep the MyClass alive (the MyClass keeps it alive, but when the MyClass goes, so does the lambda). This isn't enough to answer "Will 'o' be eligible for garbage collection here?" though.
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 is seemingly the simplest, but actually isn't, so we'll come back to it.
In Meth1, the implementation is likely to be leaving local storage as it is, since it hasn't needed any more, so while o
is not in scope, the implementation will still have that local storage in use.
In Meth2, the implementation could use the same local storage it was using for o
for i
so even though it's still in scope, it would be eligible to collect ("in scope" means the programmer could choose to do something with it, but he or she didn't and the compiled code has no need for such a concept).
Meth3 is more likely to re-use the storage, because the extra temporary uses of it make that implementation versus setting aside all storage to begin with, make more sense.
None of these things have to be this way. Meth2 and Meth3 could both set-aside all storage needed for the method at the beginning. Meth1 could reuse the storage because it makes no difference to re-order i
and o
being assigned.
Meth0 is more complicated because it could depend on what the calling method does next with local storage, rather than there being a clean-up at the time (both are legitimate implementations). IIRC there always is a clean-up with the current implementation, but I'm not sure and it doesn't matter anyway.
In all, scope isn't the relevant thing, but whether the compiler and later the JITter can and does make use of the local storage related to an object. It's even possible to clean an object up before methods and properties of it are invoked (if those methods and properties don't make use of this
or any of the object's fields, because it'll work just fine if the object has been deleted!).
When the runtime leaves that block of code, there are no more references to the object, so it gets garbage collected.
It depends on what exactly you mean by "local context". Compare:
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
}
To:
static void Main(string[] args)
{
var r = new Random();
{
var o = new MyClass();
o.MyClassEvent += a => { };
}
GC.Collect(); //no, it wasn't collected
}
In my environment (Debug build, VS 2010, Win7 x64), the first one was eligible for GC and the second one wasn't (as checked by having MyClass take up 200MB of memory and checking that in Task Manager), even though it was out of scope. I suppose this is because the compiler declares all local variables at the start of a method, so to the CLR, o
is not out of scope, even though you couldn't use it in the C# code.