1

I have come across something strange when writing unit-tests where I want to test that object can really be garbage collected (cause all events are clean up properly).

To have a minimal example I created a console project in visual-studio 2013 (Target Framework: 4.5.1) with the following code:

using System;

namespace GarbageCollectionFinalizerForLoop
{
    class TestObj
    {
        ~TestObj()
        {
            Program.DestructorWasCalled = true;
        }
    }

    class Program
    {
        public static bool DestructorWasCalled = false;

        static void Main(string[] args)
        {
            object myTestObj = new TestObj();

            myTestObj = new object();

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.WaitForFullGCComplete();
            GC.Collect();

            for (int i = 0; i < 1; i++)
            {

            }

            Console.WriteLine("Finalizer was called: " + DestructorWasCalled);
            Console.ReadLine();


        }
    }
}

It turns that the finalizer of the TestObj is not called when there is this useless for-loop. When the for-loop is commented out the destructor is called. So the for-loop seems to prevent that the garbage collector can collect this instance of TestObj. Multiple test yield the same result and this behavior seem 100% deterministic.

Then I switched the Target Framework to 3.5 and all of a sudden it worked as expected (the destructor function got called). Then I changed the Target Framework back to 4.5.1 and it still worked.

I was puzzled and tried project clean/rebuild/deleting file but I could not get back the old behavior. But when I created again a new project with the identical code I get back the buggy behavior... So it seems that switching to Target Framework 3.5 made the code run as expected even if you later switch back to 4.5.1. This is really strange to me.

I tried some more things (in parentheses the console output):

create project with 4.5.1 (FALSE), switch to 4.5 (FALSE), switch to 4.5.1 (FALSE)

create project with 4.5.1 (FALSE), switch to 4 (TRUE), switch to 4.5.1 (TRUE)

create project with 4.5.1 (FALSE), switch to 3.5 (TRUE), switch to 4.5.1 (TRUE)

create project with 4.5.1 (FALSE), switch to 3 (TRUE), switch to 4.5.1 (TRUE)

create project with 4.5.1 (FALSE), switch to 2 (TRUE), switch to 4.5.1 (TRUE)

Something strange is happening here. Does anybody have more information on this issue?

EDIT:

The behavior was the same for DEBUG and RELEASE builds and the "problem" only affects DEBUG build and RELEASE builds run from withing visual-studio.

Sjoerd222888
  • 3,228
  • 3
  • 31
  • 64
  • 1
    Have you tried both DEBUG and RELEASE build? – Lasse V. Karlsen Nov 26 '14 at 16:39
  • Yes. Same behavior with DEBUG and RELEASE builds. – Sjoerd222888 Nov 26 '14 at 16:39
  • 2
    And did you run it in the debugger or standalone? – Lasse V. Karlsen Nov 26 '14 at 16:43
  • I just tired, running the DEBUG build yields FALSE, running the RELEASE build yields TRUE. So the RELEASE build works as expected when NOT run from within VS. – Sjoerd222888 Nov 26 '14 at 16:46
  • 1
    When you run your program through Visual Studio, by default it'll attach a debugger to the process. When being debugged, the CLR will extend the lifetime of variables. Either run the program from outside Visual Studio or start it with Ctrl+F5. Read Eric Lippert's [C# Performance Benchmark Mistakes, Part One](http://tech.pro/blog/1293/c-performance-benchmark-mistakes-part-one). In particular, Mistake #3: Running your benchmark in the debugger. – dcastro Nov 26 '14 at 16:48
  • consider also that finalizers are called on different thread, so you *may* try to use `volatile` with your boolean, to ensure that *result* is correct and run it as standalone program, to avoid vs influence. – Tigran Nov 26 '14 at 16:49
  • @Lasse V. Karlsen: This answer does indeed point out some interessting stuff and the answer there makes a lot of sense, so thanks! But in this case here the behavior depends on the Target Framework and works as expected when using Target Framework 4 or below. So for people running Unit Tests from withing VS this is an issue that only appears with 4.5 and 4.5.1 . – Sjoerd222888 Nov 26 '14 at 16:53
  • This "problem" has always been present in .NET. There may be ways that the unit-test framework shims out to testing code under a different framework, but for a process, artificially extending the lifetime of a variable when compiled for DEBUG or run under a debugger has always been present. – Lasse V. Karlsen Nov 26 '14 at 16:59
  • Yeah, but with .Net 4 and below there is no "Problem". So I would like the .Net version 4.5/4.5.1 to behave the same way. At least it's worth mentioning that there is a difference between framework versions. – Sjoerd222888 Nov 26 '14 at 17:02
  • @Tigran: using volatile does not help. – Sjoerd222888 Nov 26 '14 at 17:07

0 Answers0