I have been reading the Garbage Collection chapter from Jeffrey Richter's fine book, "CLR via C#". There, he illustrated an example of how the GC conceptually works (how roots are marked) by referring to a disassembly listing of native code emitted from the JIT compiler. From that example, it occurred to me that nesting reference types in scope seems to have ZERO effect on expediting the garbage collection of the nested variable. I wonder if I am understanding this correctly. In any case, consider these 2 versions of code:
A) Nesting a reference type variable (y) in an inner scope:
namespace scope
{
class A { public void foo() { } }
class Program
{
static void Main(string[] args)
{
A x = new A();
x.foo();
{
A y = new A();
y.foo();
}
}
}
}
B) Same as above, except that x and y are in the same scope.
namespace scope
{
class A { public void foo() { } }
class Program
{
static void Main(string[] args)
{
A x = new A();
x.foo();
A y = new A();
y.foo();
}
}
}
Out of curiosity, I checked the generated IL code for both versions, and they are the SAME!
Q1: So, this seems to imply that indeed, scoping doesn't expedite garbage collection in any way. Is this correct? (Note: I know about the "using" statement - but I'm only curious about the behaviour on the GC of "plain old scoping" as illustrated in the 2 examples above.)
Q2: If the answer to Q1 is "True", then I am utterly perplexed by how a "Object Lifetime is Not Determined by Scope" situation can possibly happen, as described here: http://www.curly-brace.com/favorite.html
Garbage collection partly depends on whether you're running in the debugger or not. I'll assume we're not.
It can actually be much more aggressive than you think. For example:
The object can be garbage collected immediately after the second line - i.e. long before the variable
o
exits scope. This would also be true without the final line.In other words, scoping doesn't expedite garbage collection - because the GC is already smarter than you might think.
In fact, the garbage collector can be even more aggressive. Consider this code:
where Foo looks like this:
In theory, the garbage collector can collect the
Foo
object whileSomeMethod
is running so long as it's gone past the first line, where it actually reads fromx
. IfFoo
had a finalizer, you could have the finalizer running in one thread whileSomeMethod
was running in another. Scary stuff.The author of the linked page appears to think that C# is (or should be) in some way related to C++. From such a position it is not surprising that 'astonishing' (in the Principle of Least Astonishment sense) things follow. In particular,
should suggest immediately (at a really quite basic level of knowledge of .NET) that
ReadAccessor
implementIDisposable
.Jon is, of course correct. For some additional background I refer you to the C# specification, which states:
and, also important:
Notice here that we are drawing a distinction between the lifetime of the object being referenced -- that is, when the garbage collector is allowed to step in -- and the lifetime of the variable holding the reference. As Jon points out, the lifetime of both the variable and the object it refers to can be aggressively shortened by the compiler and the garbage collector both if we can prove that doing so does not release a reference that someone could still be holding on to.
Object life-time doesn't need scope, it is based on references to that object and it's current generation count in the GC - once it is completely dereferenced, it's time-to-GC just depends on its generation.
Method scoped references will simply be completely dereferenced by the end of the method (in your example).
Update: or as Jon points out, if it's completely derefenced before then, it is elligible for GC.