I have a class:
public class SomeClass {
public int I;
public SomeClass(int input) {
I = input;
Console.WriteLine("I = {0}", I);
}
~SomeClass() {
Console.WriteLine("deleted");
}
public void Foo() {
Thread.Sleep(1000);
Console.WriteLine("Foo");
}
}
and this program:
class Program {
static void Main(string[] args) {
new Thread(() => {
Thread.Sleep(100);
GC.Collect();
}) { IsBackground = true }.Start();
new SomeClass(10).Foo();
// The same as upper code
// var t = new SomeClass(10);
// t.Foo();
}
}
When i run this code in Debug mode, i have next result:
I = 10
Foo
deleted
but, when i change mode to Release, result changes to:
I = 10
deleted
Foo
As i understand, there is difference with call
and callvirt
: when optimization starts in Release mode, compiler look at Foo
method and can't find any reference to SomeClass
in this method, and that's why this method calls as static method by address, and garbage collector could collect this object.
Otherwise, if i change Foo
method (for example, add Console.WriteLine(I)
into this method), compiler won't decide to call this method as call
and it should be called by pointer to instance with callvirt
and garbage collector won't collect this object.
Can you please explain more clearly, what's going on here (why GC could collect object and if it's so, how does method calls).
I doubt that it's really anything to do with call
and callvirt
.
I strongly suspect it's simply because you're not using any fields within SomeClass.Foo
. The garbage collector is free to collect an object when it is certain that none of the data will be referenced again - so no code is going to look at any references to the object, or any fields within the object.
Basically, if you write a finalizer and you need to ensure that the object isn't finalized while methods within that object are running, you need to be very careful. You can use GC.KeepAlive(this)
at the end of methods, as one approach, but it's a bit ugly. I would personally try very hard to avoid needing a finalizer at all, if you can. I can't remember the last time I wrote one. (See Joe Duffy's blog for more.)
When there's a debugger attached, the GC is much less aggressive about what it can collect - after all, if you can break into the debugger at any point and inspect the fields of any object that is the target of a running instance method, that removes the possibility of garbage collecting those objects.
See more on this question at Stackoverflow