如何将 WeakReference<Action> 保留到实例的方法,直到收集实例为止?

Pet*_*ris 2 c# weak-references

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() { }
}
Run Code Online (Sandbox Code Playgroud)

输出显示inst不为空,但指向的引用WeakReference<Action>为空。我预计这是因为创建了一个指向实例方法的新操作,而不是存储对实例方法本身的引用。

如何在对象实例尚未被垃圾回收期间保留对该对象实例的方法的弱引用?

use*_*407 5

如果您需要在实例Action之前不收集该实例SomeClass,那么您需要添加SomeClass实例之间的引用Action。它可以是SomeClass指向Action实例的实例字段,但如果您无法更改SomeClass定义,则可以使用ConditionalWeakTable<TKey,TValue>类动态附加字段。

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}");
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

 True: True: True
 True: True:False
False:False:False
Run Code Online (Sandbox Code Playgroud)

运行tio

DelegateKeeper类中,我使用List<Delegate>作为依赖对象类型,因此您可以为每个类实例保留多个委托。我使用Delegate.Target作为表的键,因此您不需要单独传递实例。这不适用于匿名方法,因为它们可能在Target属性中具有编译器生成的闭包类。GetOrCreateValue获取绑定到键的值或使用默认构造函数创建新值并将其自动添加到表中。