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
将不存在,因此将按预期工作。
我正在尝试使用事件聚合器并将操作存储在包装器 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
将不存在,因此将按预期工作。