WeakReference的行动目标始终是活着的匿名行动

问题描述:

我想我的手在一个事件聚合和我存储在那里我存储的实际操作方法为Delegate一个包装类行为,类操作方法作为WeakReferenceWeakReference的行动目标始终是活着的匿名行动

当我要调用的事件处理程序我第一次检查,如果WeakReference.IsAlive是真实的。如果是的话我会调用它,否则我会将它从收集中移除,因为它意味着它所属的对象已被清空/垃圾收集。

然而,当我创建一个匿名的行动,并添加到我的事件聚合器它始终是活着,即使我空父类(和forcebly运行垃圾收集器)。

如何让匿名方法的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处理器(因此,不要使用“本”),因为这样的假设导致观察到的行为。

要完全明白其中的道理,最简单的方法是查找这是由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),其中有一个静态字段,引用此类的实例,以及另一个静态字段,其中缓存对匿名处理程序的引用。因此,您的匿名委托实际上是实例类GeneratedClass的实例方法(名为Handler),该实例存储在静态字段中。

在这一点上,你应该已经意识到你为什么会观察到这种行为。你引用

_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将是无效的方法不适用于静态方法的工作,所以你的代码将失败我想)。

现在,让我们改变这个有点:

class TempObject { 

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

    public string Name { get; set; } 
} 

这个时候你的处理器确实参考“本”,所以它是关系到TempObject类的一个实例。编译器会为这个sitatation生成不同的代码,我不会在这里显示,但最终的结果是,在这种情况下,当TempObject被垃圾收集时,你的WeakReference将不会活着,所以会按预期工作。

+0

感谢您的详细回复:)我注意到,如果我添加了一个“this”的引用,当TempObject得到GC时,它得到GC'ed。然而,我有一些用例,我不会引用“this”,这意味着将创建静态的GeneratedClass。当TempObject被GC'ed时,没有办法删除这个静态引用吗?如果存在,我怎么才能确保'_reference.Target!= null'即使它是静态的?换句话说,我怎样才能为这个问题创建一个解决方法,而不需要引用“this”? – GTHvidsten

+1

不,没有办法删除它,因为它是只读的(即使你设法使用反射)。通过传递常规静态方法的同样的故事 - 他们的目标是空的,你不能做任何事情。我建议你明确地允许传递“owner”对象:AddHandler(owner,(0)=> ....)。当存在时 - 将其用于你的弱点。如果你选择了这个选项,它仍然可能会误用你的api,所以你可能会考虑使它成为必需的。 – Evk

+0

我想我现在明白了。出于好奇,这会发生在所有匿名方法中,还是只发生在我的事件处理程序保持参考的地方?如果这种情况发生,无论我的事件处理程序,这不会是一种在C#.Net中的内存泄漏吗? – GTHvidsten