I had a unit test that had been passing for months and months (years?) suddenly start failing on me last week in debug builds only (Visual Studio 2017 15.7.5, .net framework 4.5). It relies on an object referenced by a local variable becoming garbage after that variable was set to null. I was able to distill things down to the below (no test framework required):
private class Foo
{
public static int Count;
public Foo() => ++Count;
~Foo() => --Count;
}
public void WillFail()
{
var foo = new Foo();
Debug.Assert(Foo.Count == 1);
foo = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Debug.Assert(Foo.Count == 0);
}
Setting a breakpoint on the second assert and taking a memory snapshot shows that there is indeed one Foo
object in memory, and its root is "local variable." Enclosing the first three lines in their own set of {}s doesn't make any difference, but extracting them into a local function allows the test to pass:
public void WillPass()
{
DoIt();
GC.Collect();
GC.WaitForPendingFinalizers();
Debug.Assert(Foo.Count == 0);
void DoIt()
{
var foo = new Foo();
Debug.Assert(Foo.Count == 1);
}
}
What gives? I was under the impression that objects became garbage the moment the last reference to them went away--more than that; I've been warned by several authors that objects can become garbage while there are still references to them in scope, as long as those references aren't used again. And the fact that this test used to work suggests I was right. But now it looks like an object referenced by a local variable (at least) isn't garbage until the end of the function that contains the variable. Did something change?