Whilst ‘investigating’ finalisation (read: trying stupid things) I stumbled across some unexpected behaviour (to me at least).
I would have expected the Finalise method to not be called, whereas it gets called twice
class Program
{
static void Main(string[] args)
{
// The MyClass type has a Finalize method defined for it
// Creating a MyClass places a reference to obj on the finalization table.
var myClass = new MyClass();
// Append another 2 references for myClass onto the finalization table.
System.GC.ReRegisterForFinalize(myClass);
System.GC.ReRegisterForFinalize(myClass);
// There are now 3 references to myClass on the finalization table.
System.GC.SuppressFinalize(myClass);
System.GC.SuppressFinalize(myClass);
System.GC.SuppressFinalize(myClass);
// Remove the reference to the object.
myClass = null;
// Force the GC to collect the object.
System.GC.Collect(2, System.GCCollectionMode.Forced);
// The first call to obj's Finalize method will be discarded but
// two calls to Finalize are still performed.
System.Console.ReadLine();
}
}
class MyClass
{
~MyClass()
{
System.Console.WriteLine("Finalise() called");
}
}
Could anyone explain whether this behaviour is intentional and if so why?
This above code was compiled in x86 debug mode and running on the CLR v4.
Many thanks
I can guess… and this really is only a guess. As Eric says, don’t break the rules like this 🙂 This guess is only for the sake of idle speculation and interest.
I suspect that there are two data structures involved:
When the GC notices that an object is eligible for garbage collection, I suspect it checks the object’s header and adds the reference to the finalization queue. Your calls to
SuppressFinalizationare preventing that behaviour.Separately, the finalizer thread runs through the finalization queue and calls the finalizer for everything it finds. Your calls to
ReRegisterForFinalizeare bypassing the normal way that the reference ends up on the queue, and adding it directly.SuppressFinalizationisn’t removing the reference from the queue – it’s only stopping the reference from being added to the queue in the normal way.All of this would explain the behaviour you’re seeing (and which I’ve reproduced). It also explains why when I remove the
SuppressFinalizationcalls I end up seeing the finalizer called three times – because in this case the “normal” path adds the reference to the finalization queue as well.