对于匿名操作,WeakReference to Action目标始终处于活动状态

时间:2016-11-06 15:49:11

标签: c# delegates garbage-collection action weak-references

我正在尝试使用事件聚合器,我将操作存储在一个包装类中,我将实际操作方法存储为Delegate,而action方法所在的类为{{ 1}}。

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

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

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

WeakReference

在测试应用程序中

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

1 个答案:

答案 0 :(得分:1)

Bellow我假设你没有在你的匿名TempObject中引用任何实例变量(所以,不要使用&#34;这个&#34;)处理程序,因为这种假设会导致观察到的行为。

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

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);
        }
    }
}

你在这里看到编译器生成了一个新类(这里名为GeneratedClass),它有一个 static 字段,引用了这个类的一个实例,还有另一个静态字段,其中引用缓存您的匿名处理程序。因此,您的匿名委托实际上是类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,因此我认为你的代码会失败)。< / p>

现在让我们改变一下:

class TempObject {

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

    public string Name { get; set; }
}

这次你的处理程序 引用&#34;这个&#34;所以它与TempObject类的实例有关。编译器将为此坐标生成不同的代码,我不会在这里显示,但最终结果是,在这种情况下,当TempObject被垃圾收集时,WeakReference将不会存活,因此将按预期工作。