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?
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();
}