Search code examples
c#delegatesweak-references

GC doesn't collect when WeakReference references a delegate?


It seems to me that when I use the WeakReference class on a delegate method of an object class, the object class gets collected by the GC but there is still another copy of it residing in the WeakReference?

I find it difficult to explain in words. I will give an example. I have the following object class called TestObject:

class TestObject
{
    public string message = "";

    private delegate string deleg();

    public TestObject(string msg)
    {
        message = msg;
    }

    public Delegate GetMethod()
    {
        deleg tmp = this.TestMethod;
        return tmp;
    }

    public string TestMethod()
    {
        return message;
    }

}

Now, in my main application, I attempt to refer to the method TestMethod in TestObject via a WeakReference. The intention is so that the TestObject can be collected by GC when all hard references are gone. This is how my main application looks like:

static void Main(string[] args)
    {
        var list = new List<WeakReference>();
        var obj = new TestObject("Hello 1");
        list.Add(new WeakReference(obj.GetMethod()));
        Console.WriteLine("Initial obj: " + ((Delegate)list[0].Target).DynamicInvoke());      //Works fine
        obj = null;     //Now, obj is set to null, the TestObject("Hello 1") can be collected by GC
        GC.Collect();   //Force GC
        Console.WriteLine("Is obj null: " + ((obj) == null ? "True" : "False"));
        Console.WriteLine("After GC collection: " + ((Delegate)list[0].Target).DynamicInvoke());
        Console.ReadKey();
    }

This is the output when I run the above code:

enter image description here

Here's the weird thing. On the first line, obj could print "Hello 1" because it was just initialised and obj was holding a reference to the TestObject. All correct. Next, obj was set to null with obj = null and GC was forced to collect. So, in the second line of the output, obj is true to be null. Finally on the last line, since the GC has collected the obj away, I'm expecting it to either throw a NullReferenceException or just print nothing on the output. However, it actually printed the same thing as on the first line of the output! Shouldn't the TestObject be collected by the GC at this point already?!

This begs the question if the TestObject that was first held in obj was later collected by the GC or not after I set obj to null.

If I had passed the whole object into WeakReference, ie, new WeakReference(obj), instead of a delegate into the WeakReference, it would have worked perfectly.

Unfortunately, in my code, I need to pass into the WeakReference a delegate. How can I have the WeakReference to work correctly so that the GC can collect away the object by only having referenced to a delegate?


Solution

  • I think the problem is in your test - not in the framework. It appears that setting the local variable to null doesn't do what you're expecting. If we skip the local variable entirely, we get the expected NullReferenceException on the 'after' line:

    static void Main(string[] args)
    {
        var list = new List<WeakReference>();
        //var obj = new TestObject("Hello 1");
        list.Add(new WeakReference(new TestObject("Hello 1").GetMethod()));
        Console.WriteLine("Initial obj: " + ((Delegate)list[0].Target).DynamicInvoke());      //Works fine
        //obj = null;     //Now, obj is set to null, the TestObject("Hello 1") can be collected by GC
        GC.Collect();   //Force GC
        //Console.WriteLine("Is obj null: " + ((obj) == null ? "True" : "False"));
        Console.WriteLine("After GC collection: " + ((Delegate)list[0].Target).DynamicInvoke());
        Console.ReadKey();
    }