在收集实例之前,如何将 WeakReference<Action> 保存到实例的方法?
How can I hold a WeakReference<Action> to a method of an instance until the instance is collected?
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() { }
}
输出显示inst
不为null,但WeakReference<Action>
指向的引用为。我预计这是因为创建了一个指向实例方法的新 Action,而不是存储对实例方法本身的引用。
在实例尚未被垃圾回收期间,如何保持对对象实例方法的弱引用?
从 MethodGroup 创建一个 Action
实例会创建一个不以任何方式附加到该方法所属的 class 实例的实例。这意味着没有什么可以让你的 Action
root,GC 会很乐意收集它。
如果您将对 Action
的引用存储为 class 的成员,它将植根于该实例的生命周期,只要该实例还在,它就不会被收集还活着。
public class SomeClass
{
public SomeClass()
{
DoSomething = this.DoSomething_Internal ;
}
public Action DoSomething { get; }
private void DoSomething_Internal() { }
}
注意:我将原来的 DoSomething
设为私有,并将其重命名为 DoSomething_Internal
,将旧的 DoSomething
替换为只读的 属性 以保留 class 签名尽可能接近你原来的 class。您不需要完全遵循此模式,对存储在 class 中的 Action
的任何引用,包括在普通字段中,都可以。如果您真的想使用它,您仍然必须以某种方式公开该引用。
你可以这样测试:
var inst = new SomeClass();
var weakRef = new WeakReference<Action>(inst.DoSomething);
GC.Collect();
GC.WaitForPendingFinalizers(); // You should do this after forcing a GC, just in case there is still GC work being done in the background.
Console.WriteLine($"inst is alive = {inst != null} : weakRef.Target is alive = {weakRef.TryGetTarget(out Action callback1)}");
// These next 2 lines discard local variables, as Hans points out in the comments,
// DO NOT do this in production code. Please read the link he posted for more details.
inst = null; // discard the class instance
callback1 = null; // discard the temporary Action instance from TryGetTarget, otherwise it will act as a GC Root, preventing it from being collected later.
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine($"inst is alive = {inst != null} : weakRef.Target is alive = {weakRef.TryGetTarget(out Action callback2)}");
产生以下输出:
inst is alive = True : weakRef.Target is alive = True
inst is alive = False : weakRef.Target is alive = False
如果您需要 Action
实例不会在 SomeClass
实例之前被收集,那么您需要添加从 SomeClass
实例到 Action
实例的引用。可以是指向Action
实例的SomeClass
实例字段,但是如果你不能改变SomeClass
定义,那么你可以使用ConditionalWeakTable<TKey,TValue>
class来附加动态字段。
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}");
}
}
输出:
True: True: True
True: True:False
False:False:False
在 DelegateKeeper
class 中,我使用 List<Delegate>
作为依赖对象类型,因此您可以为每个 class 实例保留多个委托。我使用 Delegate.Target
作为 table 的键,因此您不需要单独传递实例。这不适用于匿名方法,因为它们可能让编译器在 Target
属性 中生成闭包 class。 GetOrCreateValue
获取绑定到键的值或使用默认构造函数创建新值并自动将其添加到 table。
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() { }
}
输出显示inst
不为null,但WeakReference<Action>
指向的引用为。我预计这是因为创建了一个指向实例方法的新 Action,而不是存储对实例方法本身的引用。
在实例尚未被垃圾回收期间,如何保持对对象实例方法的弱引用?
从 MethodGroup 创建一个 Action
实例会创建一个不以任何方式附加到该方法所属的 class 实例的实例。这意味着没有什么可以让你的 Action
root,GC 会很乐意收集它。
如果您将对 Action
的引用存储为 class 的成员,它将植根于该实例的生命周期,只要该实例还在,它就不会被收集还活着。
public class SomeClass
{
public SomeClass()
{
DoSomething = this.DoSomething_Internal ;
}
public Action DoSomething { get; }
private void DoSomething_Internal() { }
}
注意:我将原来的 DoSomething
设为私有,并将其重命名为 DoSomething_Internal
,将旧的 DoSomething
替换为只读的 属性 以保留 class 签名尽可能接近你原来的 class。您不需要完全遵循此模式,对存储在 class 中的 Action
的任何引用,包括在普通字段中,都可以。如果您真的想使用它,您仍然必须以某种方式公开该引用。
你可以这样测试:
var inst = new SomeClass();
var weakRef = new WeakReference<Action>(inst.DoSomething);
GC.Collect();
GC.WaitForPendingFinalizers(); // You should do this after forcing a GC, just in case there is still GC work being done in the background.
Console.WriteLine($"inst is alive = {inst != null} : weakRef.Target is alive = {weakRef.TryGetTarget(out Action callback1)}");
// These next 2 lines discard local variables, as Hans points out in the comments,
// DO NOT do this in production code. Please read the link he posted for more details.
inst = null; // discard the class instance
callback1 = null; // discard the temporary Action instance from TryGetTarget, otherwise it will act as a GC Root, preventing it from being collected later.
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine($"inst is alive = {inst != null} : weakRef.Target is alive = {weakRef.TryGetTarget(out Action callback2)}");
产生以下输出:
inst is alive = True : weakRef.Target is alive = True
inst is alive = False : weakRef.Target is alive = False
如果您需要 Action
实例不会在 SomeClass
实例之前被收集,那么您需要添加从 SomeClass
实例到 Action
实例的引用。可以是指向Action
实例的SomeClass
实例字段,但是如果你不能改变SomeClass
定义,那么你可以使用ConditionalWeakTable<TKey,TValue>
class来附加动态字段。
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}");
}
}
输出:
True: True: True
True: True:False
False:False:False
在 DelegateKeeper
class 中,我使用 List<Delegate>
作为依赖对象类型,因此您可以为每个 class 实例保留多个委托。我使用 Delegate.Target
作为 table 的键,因此您不需要单独传递实例。这不适用于匿名方法,因为它们可能让编译器在 Target
属性 中生成闭包 class。 GetOrCreateValue
获取绑定到键的值或使用默认构造函数创建新值并自动将其添加到 table。