2

I've got a simple test code in Nunit+.net core 2.0 under VS2019:

public class Tests
{
    public static int i = 0;
    class First
    {
        ~First()
        {
            i += 1;
            Console.WriteLine("First's destructor is called.");
        }
    }

    class Second : First
    {
        ~Second() { i += 10; Console.WriteLine("Second's destructor is called."); }
    }

    class Third : Second
    {
        ~Third() { i += 100; Console.WriteLine("Third's destructor is called."); }
    }
    [Test]
    public static void Test()
    {
        {
            Third t = new Third();
        }
        Thread.Sleep(1000);
        System.GC.Collect();
        Assert.AreEqual(111, i);
    }
}

It always fails, while I found finally i=0, and the destructors are called right after Test(). But you can see the "t" is in a code block, and is invalid after the code block, and I also called

System.GC.Collect();

before my "Assert".

Why destructor is not called even after GC? How to fix my code to make the test pass?

Thanks a lot.

Troskyvs
  • 7,537
  • 7
  • 47
  • 115
  • 4
    You need at least a call to `GC.WaitForPendingFinalizers` in there. If you're in a Debug build, it's probably also holding onto `t` for debugging reasons: the scope of a variable is almost entirely unrelated to when it's available for GC – canton7 Jan 22 '20 at 13:58
  • In general, in C#, there is rarely a need to write a finalizer. Writing good finalizes is very hard. If you want deterministic finalization (i.e., you want cleanup code to run at a specific time) look up `IDisposable`, `using` and the Dispose pattern – Flydog57 Jan 22 '20 at 14:39

2 Answers2

1

Use using statement and the IDisposable interface instead destructor;

 public class Tests
{
    public static int i = 0;
    class First : IDisposable
    {
        public virtual void Dispose()
        {
            i += 1; Console.WriteLine("First's Dispose is called.");
        }
    }

    class Second : First
    {
        public override void Dispose()
        {
            i += 10; Console.WriteLine("Second's Dispose is called.");
            base.Dispose();
        }
    }

    class Third : Second
    {
        public override void Dispose()
        {
            i += 100; Console.WriteLine("Third's Dispose is called.");
            base.Dispose();
        }
    }
    public static void Test()
    {
        using (Third t = new Third()) {
            Console.WriteLine("Now everything will be ok, after leaving this block");
            Console.WriteLine("t object will be dispose");
        }
        Thread.Sleep(1000);
        System.GC.Collect();
        Assert.AreEqual(111, i);
    }
}

You can call System.GC.Collect() but it is not matter. The destructor will not be call immediately as you expect. (In above code you don't need System.GC.Collect())

enter image description here

It is strange to me why Microsoft does not make this clearer, instead of the old definition "Forces an immediate garbage collection of all generations." which leads developers to think they should expect an immediate result and GC.Collect() should wait for garbage collection to be complete before it returns?!

GC.Collect Method(System) - Microsoft Docs

Even more strange to me is that the code they give as an example for using of GC.Collect() don't work as they claim, for the same reason. You can try yourself by commenting GC.Collect(); line. I don't know, maybe they just don't update their documents as often. Maybe the situation was different before with GC.Collect()?! GC.CollectRequest() would be a more appropriate name for this function, if you ask me.

Strange example code on Microsoft page for GC.Collect()

P.s. I'm new to C# so please, excuse me if I say things that do not fully understand or something I'm wrong in the code I write.

0

Call GC.WaitForPendingFinalizers() after GC.Collect()

The finalizers (not destructors) run as background tasks asynchronously, so after you call GC.Collect() and reach the assertion statement the finalizers are not guaranteed to have finished.

asaf92
  • 1,557
  • 1
  • 19
  • 30