WeakReference to Action 目标对于匿名操作始终有效

WeakReference to Action target is always alive for anonymous actions

我正在尝试使用事件聚合器并将操作存储在包装器 class 中,我将实际操作方法存储为 Delegate,而 class 动作方法是 WeakReference.

当我想调用事件处理程序时,我首先检查 WeakReference.IsAlive 是否为真。如果是,则调用它,否则我将其从集合中删除,因为这意味着它所属的对象已被 nulled/garbage 收集。

但是,当我创建一个匿名操作并将其添加到我的事件聚合器时,它始终处于活动状态,即使我使父 class 无效(并强制 运行 垃圾收集器)。

如何将匿名方法上的 IsAlive 设置为 FALSE?

void AddHandler<TEvent>(Action<TEvent> callback) {
    InternalHandler handler = new InternalHandler(callback);
    // Store the handler somewhere to use the callback later, if it still alive
}

class InternalHandler {
    WeakReference _reference;
    Delegate _method;

    public InternalHandler(Delegate handler) {
        _reference = new WeakReference(handler.Target);

        Type messageType = handler.Method.GetParameters()[0].ParameterType;
        Type delegateType = typeof(Action<,>).MakeGenericType(handler.Target.GetType(), messageType);

        _method = Delegate.CreateDelegate(delegateType, handler.Method);
    }

    bool IsAlive => _reference != null && _reference.IsAlive;

    bool Invoke(object data) {
        if (!IsAlive) return false;

        if (_reference.Target != null) _method.DynamicInvoke(_reference.Target, data);

        return true;
    }
}

并在测试应用中

TempObject t = new TempObject();

// Some time later
t = null;
GC.Collect();

class TempObject {

    public TempObject() {
        myHandler.AddHandler<SomeObject>(o => {
            // Some code with a breakpoint
        });
    }
}

在调用了GC.Collect()之后发出了一个新的事件,t中匿名方法的断点仍然被调用了!

我怎样才能得到正确的 WeakReference 匿名方法?

下面我 假设 你没有在你的匿名 TempObject 处理程序中引用任何实例变量(所以,不要使用 "this"),因为这样的假设会导致观察到的行为。

要完全理解原因,最简单的方法是查看C#编译器为您提到的匿名方法生成的代码。由于使用了 classes 和变量的名称,代码相当难读,但这里是一个有点美化的版本:

class TempObject {
    public TempObject(Handlers handlers) {
        handlers.AddHandler<object>(GeneratedClass._staticAction ?? (GeneratedClass._staticAction = GeneratedClass._staticField.Handler));
    }

    [CompilerGenerated]
    [Serializable]
    private sealed class GeneratedClass {
        public static readonly TempObject.GeneratedClass _staticField;
        public static Action<object> _staticAction;

        static GeneratedClass() {
            TempObject.GeneratedClass._staticField = new TempObject.GeneratedClass();
        }

        public GeneratedClass() {

        }

        internal void Handler(object o) {
            Console.WriteLine(o);
        }
    }
}

你在这里看到编译器生成了新的 class(这里命名为 GeneratedClass),它有一个 static 字段引用了这个实例class,还有另一个静态字段,其中缓存了对匿名处理程序的引用。因此,您的匿名委托实际上是 class GeneratedClass 实例的实例方法(名为 Handler),该实例存储在 static 字段中。

此时您应该已经意识到为什么会观察到这种行为。您的参考是

 _reference = new WeakReference(handler.Target);

并且 handler.Target 在这种情况下引用静态字段,该字段永远不会设置为 null,因此永远不会被垃圾收集。

另一种直观理解的方法是,您从不在匿名处理程序中使用与 TempObject 实例 相关的任何内容,因此您的匿名方法基本上是一个引用静态方法(概念上),而不是实例方法。所以它实际上与 TempObject 个实例没有任何关系,并且它的生命周期与 TempObjects 的生命周期无关。所以基本上它是一样的:

class TempObject {
    public TempObject(Handlers handlers) {
        handlers.AddHandler<object>(Handler);
    }

    private static void Handler(object arg) {
        Console.WriteLine(arg);
    }
}

当然,您的方法不能按预期用于静态方法也就不足为奇了(实际上对于静态方法,handler.Target 将为 null,因此我想您的代码将失败)。

现在让我们稍微改变一下:

class TempObject {

    public TempObject(Handlers handlers) {
        handlers.AddHandler<object>(o => {
            Console.WriteLine(o + this.Name);
        });
    }

    public string Name { get; set; }
}

这次您的处理程序 确实 引用 "this",因此它与 TempObject class 的实例相关。编译器将为此站点生成不同的代码,我不会在此处显示,但最终结果将是,在这种情况下,当 TempObject 被垃圾收集时,您的 WeakReference 将不存在,因此将按预期工作。