Search code examples
c#.netgarbage-collectionweak-references

Garbage Collection should have removed object but WeakReference.IsAlive still returning true


I have a test that I expected to pass but the behavior of the Garbage Collector is not as I presumed:

[Test]
public void WeakReferenceTest2()
{
    var obj = new object();
    var wRef = new WeakReference(obj);

    wRef.IsAlive.Should().BeTrue(); //passes

    GC.Collect();

    wRef.IsAlive.Should().BeTrue(); //passes

    obj = null;

    GC.Collect();

    wRef.IsAlive.Should().BeFalse(); //fails
}

In this example the obj object should be GC'd and therefore I would expect the WeakReference.IsAlive property to return false.

It seems that because the obj variable was declared in the same scope as the GC.Collect it is not being collected. If I move the obj declaration and initialization outside of the method the test passes.

Does anyone have any technical reference documentation or explanation for this behavior?


Solution

  • Hit the same issue as you - my test was passing everywhere, except for under NCrunch (could be any other instrumentation in your case). Hm. Debugging with SOS revealed additional roots held on a call stack of a test method. My guess is that they were a result of code instrumentation that disabled any compiler optimizations, including those that correctly compute object reachability.

    The cure here is quite simple - don't ever hold strong references from a method that does GC and tests for aliveness. This can be easily achieved with a trivial helper method. The change below made your test case pass with NCrunch, where it was originally failing.

    [TestMethod]
    public void WeakReferenceTest2()
    {
        var wRef2 = CallInItsOwnScope(() =>
        {
            var obj = new object();
            var wRef = new WeakReference(obj);
    
            wRef.IsAlive.Should().BeTrue(); //passes
    
            GC.Collect();
    
            wRef.IsAlive.Should().BeTrue(); //passes
            return wRef;
        });
    
        GC.Collect();
    
        wRef2.IsAlive.Should().BeFalse(); //used to fail, now passes
    }
    
    private T CallInItsOwnScope<T>(Func<T> getter)
    {
        return getter();
    }