5

Suppose I have a WeakReference of a target strong reference. I'd like to be informed when the target object itself is being collected by the GC. Is it possible?

EDIT: Adding code to the finalizer/destructor is not an option here. I need something that is not dependent on class code.

Cœur
  • 37,241
  • 25
  • 195
  • 267
ceztko
  • 14,736
  • 5
  • 58
  • 73
  • I don't believe there's any event raised which indicates an address is being collected. The closest thing you might try is creating a Finalizer on the type being referenced, though they run before an unspecified time before collection occurs. I look forward to somebody smarter giving an answer. – Paul Turner Dec 28 '11 at 10:44
  • 3
    The destructor of your object is called if the garbage collector destroys it. You could react in the destructor call and send a self made event for instance. – juergen d Dec 28 '11 at 10:45
  • 1
    Sounds like a recipe for resurrection. Why do you need this? – Ani Dec 28 '11 at 10:53
  • Sorry, but I need something more transparent and not dependent on class code. Maybe it's just not possible without hacks. I need this to keep track of detached instances of objects in a custom serialization framework: when objects are collected, I'd like to clean a static map of `Guid` <-> `WeakReference`. Without this, I can just check that the objects are collected by manually testing the weak references at a deferred time. Objects implements an interface so the framework is more flexible, so adding a finalizer is not an option. – ceztko Dec 28 '11 at 11:04

3 Answers3

7

It's possible under .NET 4.0 and following using ConditionalWeakTable<TKey, TValue>. Thanks this, and other sites. It follows proof of concept code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

namespace Test
{
    public static class GCInterceptor
    {
        private static ConditionalWeakTable<object, CallbackRef> _table;

        static GCInterceptor()
        {
            _table = new ConditionalWeakTable<object, CallbackRef>();
        }

        public static void RegisterGCEvent(this object obj, Action<int> action)
        {
            CallbackRef callbackRef;
            bool found = _table.TryGetValue(obj, out callbackRef);
            if (found)
            {
                callbackRef.Collected += action;
                return;
            }

            int hashCode = RuntimeHelpers.GetHashCode(obj);
            callbackRef = new CallbackRef(hashCode);
            callbackRef.Collected += action;
            _table.Add(obj, callbackRef);
        }

        public static void DeregisterGCEvent(this object obj, Action<int> action)
        {
            CallbackRef callbackRef;
            bool found = _table.TryGetValue(obj, out callbackRef);
            if (!found)
                throw new Exception("No events registered");

            callbackRef.Collected -= action;
        }

        private class CallbackRef
        {
            private int _hashCode;
            public event Action<int> Collected;

            public CallbackRef(int hashCode)
            {
                _hashCode = hashCode;
            }

            ~CallbackRef()
            {
                Action<int> handle = Collected;
                if (handle != null)
                    handle(_hashCode);
            }
        }
    }
}

Tested with the following code:

public partial class Form1 : Form
{
    private object _obj;

    public Form1()
    {
        InitializeComponent();

        _obj = new object();

        _obj.RegisterGCEvent(delegate(int hashCode)
        {
            MessageBox.Show("Object with hash code " + hashCode + " recently collected");
        });
    }

    private void button1_Click(object sender, EventArgs e)
    {
        _obj = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}
ceztko
  • 14,736
  • 5
  • 58
  • 73
2

What about Object.Finalize() method? Won't that be invoked upon finalization?

Tomislav Markovski
  • 12,331
  • 7
  • 50
  • 72
George
  • 255
  • 3
  • 18
  • This is correct. The destructor implicitly calls `Object.Finalize()`, so overriding this method and signaling from here will give the exact time the object is garbage collected. – Tomislav Markovski Dec 28 '11 at 10:51
  • Sorry, but I need something more transparent and not dependent on class code. Maybe it's just not possible. – ceztko Dec 28 '11 at 10:55
  • 1
    This works unless a call to `GC.SupressFinalize` is made, as is usually the case for Disposeable types. Additionally, the exact time when the finalizer executes during garbage collection is undefined. – Paul Turner Dec 28 '11 at 10:56
  • what about AOP? maybe it's gonna help you somehow. – George Dec 28 '11 at 11:24
0

You may use interception to catch Finalize for each classes which is inherited from a custom interface/class. I think, this is what you want to try to achieve, right? You can use Unity for that. Here is a very short example how to do interception with Unity.

Osman Turan
  • 1,361
  • 9
  • 12
  • Correct (actually, it is an interface, so it's explained why I can't just write code in base class finalizer). Is Unity something that is built-in in .NET 3.5? – ceztko Dec 28 '11 at 11:11
  • According to [here](http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=15104), it supports .NET 3.5 SP1 as well. But, I've used it in .NET 4.0. Also, there are others IoC libraries around to web. But, I've found Unity is more robust solution. – Osman Turan Dec 28 '11 at 11:15
  • Can you confirm me that all Unity methods (TransparentProxyInterceptor , InterfaceInterceptor, VirtualMethodInterceptor) require using proxy classes? In this case, unfortunately, the changes to my existing code would be too invasive. – ceztko Dec 28 '11 at 11:44
  • I mean: `var repository = Container.Instance.Resolve();` isn't actually using a proxy reference? Can't I just have a reference, doesn't matter how I got it (constructor, manual allocation plus initialization with reflection...) and inject code for methods calls on that reference or all references of the class? – ceztko Dec 29 '11 at 11:34
  • Resolve methods rely on registered types in Unity container. So, you can't resolve any class which you have just simply a reference. – Osman Turan Dec 29 '11 at 11:38
  • Thanks, that's what I wanted to know. Unfortunately this means that what I really want is some form of runtime code injection. Unsure if this is possible or recommendable. – ceztko Dec 29 '11 at 11:46