10

I've a problem with WeakReferences in .NET 4.x, I was running tests to make sure some objects were not referenced anymore (using WeakReferences) and I noticed the behavior is not consistent across framework versions:

using System;
using System.Text;
using NUnit.Framework;

[TestFixture]
public class WeakReferenceTests
{
    [Test]
    public void TestWeakReferenceIsDisposed()
    {
        WeakReference weakRef = new WeakReference(new StringBuilder("Hello"));

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

        var retrievedSb = weakRef.Target as StringBuilder;
        Assert.That(retrievedSb, Is.Null);
    }
}

Results:

.NET 2.0  PASS
.NET 3.0  FAIL
.NET 3.5  PASS
.NET 4.0  FAIL
.NET 4.5  FAIL

Is this documented somewhere?

Is there a way to force the GC to collect that reference in .NET 4.5?

Thanks in advance.

Guillaume86
  • 14,341
  • 4
  • 53
  • 53
  • 5
    Just a note: GC does not collect unreachable objects in debug mode. – tukaef May 27 '13 at 10:38
  • Hmm, the code you've shown works for me on all versions of the framework. (Well, I don't have NUnit installed, so I'm just using `Debug.Assert` but that should not change the behavior.) – Cody Gray - on strike May 27 '13 at 10:45
  • 1
    Not sure if .NET platform is the best solution if you need to precisely control disposing of objects... Every learning resource says quite clearly - you can't predict when GC happens nor should you try to force it... – walther May 27 '13 at 10:49
  • Just to build on what @walther mentioned, even if you do invoke the garbage collector, there is no guarantee that it will execute. – Levi Botelho May 27 '13 at 10:51
  • @Cody Gray: perhaps NUnit or NCrunch is doing something weird, I'll look into that. (walther: yes I know but it's the easiest way I found to check behavior of an external assembly.) – Guillaume86 May 27 '13 at 10:53
  • @Cody Gray You're right it's NUnit or my test runner that's messing with this. (Even compiled in DEBUG this test pass on all versions) – Guillaume86 May 27 '13 at 11:00
  • @Cody Gray post your comment as an answer since it's the solution, I'll accept it – Guillaume86 May 27 '13 at 11:01
  • I use NCrunch and it was instrumenting the output assembly, producing this behavior (disabling output instrumenting make the test pass on all platforms) – Guillaume86 May 27 '13 at 11:04

2 Answers2

8

The problem here is related to NCrunch. The code works fine on my machine for all versions of the framework if I replace the test with a simple call to Debug.Assert:

using System;
using System.Text;
using System.Diagnostics;

public class WeakReferenceTests
{
    public void TestWeakReferenceIsDisposed()
    {
        WeakReference weakRef = new WeakReference(new StringBuilder("Hello"));

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

        var retrievedSb = weakRef.Target as StringBuilder;
        Debug.Assert(retrievedSb == null);
    }
}
Guillaume86
  • 14,341
  • 4
  • 53
  • 53
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
0

Thanks to @ Cody Gray (see comments), I figured that out.

I use NCrunch to run my tests and it was instrumenting the output assembly, producing this behavior (disabling output instrumenting make the test pass on all platforms).

Guillaume86
  • 14,341
  • 4
  • 53
  • 53