class Program
{
static void Main(string[] args)
{
var inst = new SomeClass();
var weakRef = new WeakReference<Action>(inst.DoSomething);
GC.Collect();
Console.WriteLine($"inst is alive = {inst != null} : weakRef.Target is alive = {weakRef.TryGetTarget(out Action callback)}");
Console.ReadLine();
}
}
public class SomeClass
{
public void DoSomething() { }
}
The output shows that inst
is not null, but the reference that WeakReference<Action>
points to is. I expect this is because a new Action is created that points to the instance method, rather than storing a reference to the instance's method itself.
How can I hold a weak reference to a method of an object instance for the duration that the instance is not yet garbage collected?
If you need that Action
instance would not be collected before SomeClass
instance, then you need to add reference from SomeClass
instance to Action
instance. It can be instance field of SomeClass
that point to Action
instance, but if you can not alter SomeClass
definition, than you can use ConditionalWeakTable<TKey,TValue>
class to attach field dynamically.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default)]
public class SomeClass {
public void DoSomething() { }
}
public static class DelegateKeeper {
private static ConditionalWeakTable<object, List<Delegate>> cwt = new ConditionalWeakTable<object, List<Delegate>>();
public static void KeepAlive(Delegate d) => cwt.GetOrCreateValue(d?.Target ?? throw new ArgumentNullException(nameof(d))).Add(d);
}
static class Program {
static void Main() {
SomeClass inst = new SomeClass();
Action a1 = inst.DoSomething;
DelegateKeeper.KeepAlive(a1);
Action a2 = inst.DoSomething;
WeakReference<SomeClass> winst = new WeakReference<SomeClass>(inst);
WeakReference<Action> wa1 = new WeakReference<Action>(a1);
WeakReference<Action> wa2 = new WeakReference<Action>(a2);
GC.Collect();
Console.WriteLine($"{winst.TryGetTarget(out _),5}:{wa1.TryGetTarget(out _),5}:{wa2.TryGetTarget(out _),5}");
GC.KeepAlive(a1);
GC.KeepAlive(a2);
GC.Collect();
Console.WriteLine($"{winst.TryGetTarget(out _),5}:{wa1.TryGetTarget(out _),5}:{wa2.TryGetTarget(out _),5}");
GC.KeepAlive(inst);
GC.Collect();
Console.WriteLine($"{winst.TryGetTarget(out _),5}:{wa1.TryGetTarget(out _),5}:{wa2.TryGetTarget(out _),5}");
}
}
Output:
True: True: True
True: True:False
False:False:False
In DelegateKeeper
class I use List<Delegate>
as dependent object type, so you can keep multiple delegates per one class instance. I use Delegate.Target
as key to the table, so you do not need to pass instance separately. This would not work with anonymous methods, since them likely to have compiler generated closure class in Target
property. GetOrCreateValue
get value bound to key or create new one using default constructor and add it to table automatically.