Is it possible to hook objects being collected by GC?
Asked Answered
P

3

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.

Paperweight answered 28/12, 2011 at 10:38 Comment(4)
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.Cartload
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.Prefigure
Sounds like a recipe for resurrection. Why do you need this?Lianaliane
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.Paperweight
P
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();
    }
}
Paperweight answered 13/3, 2012 at 20:52 Comment(2)
Works but be aware GC.Collect is not enough to force it you need to do GC.Waitfinalizers.Devious
Edited the answer. Is it ok now?Paperweight
W
2

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

Weir answered 28/12, 2011 at 10:48 Comment(4)
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.Helio
Sorry, but I need something more transparent and not dependent on class code. Maybe it's just not possible.Paperweight
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.Cartload
what about AOP? maybe it's gonna help you somehow.Weir
D
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.

Digestion answered 28/12, 2011 at 11:7 Comment(6)
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?Paperweight
According to here, 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.Digestion
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.Paperweight
I mean: var repository = Container.Instance.Resolve<CustomerRepository>(); 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?Paperweight
Resolve methods rely on registered types in Unity container. So, you can't resolve any class which you have just simply a reference.Digestion
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.Paperweight

© 2022 - 2024 — McMap. All rights reserved.