1

I think I am missing something fundamental and hope you can help. Below code creates an object, removes the reference and call the garbage collector. My expectation was that SomeClass's finalizer would be called when standing in Readline. It doesn't. I've tried calling GC.Collect in a loop, adding some Sleep() calls to have the finalizer thread started. Does not happen.

Only when the Main ends the finalizer is hit, but surprisingly it is hit twice. What am i missing?

class Program
{
    public static void Main(string[] args)
    {
        SomeClass some = new SomeClass("Hello World!");
        some = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Done");
        Console.ReadLine();
    }
}

class SomeClass 
{
    string ss;
    public SomeClass(string s) { ss = s; }
    ~SomeClass()
    {
        var hash = this.GetHashCode();
    }
}

Addendum There is a difference in running a program in debug mode versus release mode. The below program produces in debug mode Start - Done - Finalize whereas in release mode the logfile shows Start - Finalize - Done. The latter is what I expected.

class Program
{
    private static string logfile = @"c:\temp\log.txt";
    public static void Main(string[] args)
    {
        File.WriteAllText(logfile, "Start\n");
        SomeClass some = new SomeClass("Hello World!");
        some = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        File.AppendAllText(logfile, "Done\n");
    }
}

class SomeClass 
{
    private static string logfile = @"c:\temp\log.txt";
    public string SomeString { get; set; }
    public SomeClass(string s) { SomeString = s; }
    ~SomeClass()
    {
        File.AppendAllText(logfile, "Finalize\n");
    }
}
Johannes Schacht
  • 930
  • 1
  • 11
  • 28

1 Answers1

2

Objects that are garbage collected become suitable for finalization and are put on a finalization queue. There is absolutely no guarantee that those finalizers are ever going to run.

I'm going to redirect you to the great posts from Eric Lippert on this, appropriately called "When everything you know is wrong." In particular:

Myth: Setting a variable to null causes the finalizer to run on the object that was previously referenced by the variable. Setting a variable to null does not cause anything to happen immediately except changing the value of the variable. If the variable was the last living reference to the object in question then that fact will be discovered when the garbage collector runs the collector for whatever generation the object was in. (If it runs at all, which it might not. There is no guarantee that the GC runs.)

And even with GC.Collect():

Myth: Calling GC.Collect() causes finalizers to run. No, it causes a collection to happen. That might identify objects that are candidates for finalization, but it does not force the finalizer thread to be scheduled. If that’s what you want — for testing purposes only please! — then call GC.WaitForPendingFinalizers().

So the best chance of success is with GC.WaitForPendingFinalizers(). But even then, referring you to the rest of the post (and its sequel), FINALIZERS ARE NOT GUARANTEED TO RUN. Do not depend on that.

I wrote a more detailed explanation on this question so you can use that as a starting point for further research.

V0ldek
  • 9,623
  • 1
  • 26
  • 57
  • 1
    Thank you V0ldek for the valuable information. I found a hint that in debug mode things work differently. And that is indeed the case. I will add an addendum to my initial question. – Johannes Schacht Aug 16 '20 at 17:33