在 C# 中如何知道弱引用对象是否将被垃圾收集?

In c# how to know if a weak referenced object is going to be garbage collected?

假设我有这样的代码:

class Test
{
    WeakReference m_ref;

    public Test()
    {
        Test1();
        Test2();
    }

    void Test1()
    {
        m_ref = new WeakReference(new object());
    }

    void Test2()
    {
        // If I do the GC then the m_ref.Target is null
        // GC.Collect();
        Debug.Log(m_ref.Target);
    }
}

void TestFunc()
{
    new Test();
}

在这个例子中,我创建了一个新的对象实例并将其设置为 Test1 中的一个 WeakReference 实例。如果我在退出 Test1 后理解正确,则不会引用任何对象实例,因此该实例很快就会 GC

但是,在 Test2 中,如果 GC 没有执行,我仍然可以通过 m_ref.Target.

访问对象实例

有什么方法可以知道 m_ref.Target 无效 而无需 手动执行 GC?

Is there any way I could know that the m_ref.Target is invalid without manually perform the GC?

在GC回收对象之前不会无效。垃圾回收的意义在于你不知道也不必关心对象何时将被丢弃。

在你的例子中,是的,你是对的m_ref = new WeakReference(new object());执行后,实例将被收集'soon'。但是,'soon' 没有具体定义,因此您不能假定这会在调用 Test2 和执行 Debug.Log(m_ref.Target); 之前发生。

不,你不能。按照设计,WeakReference 与垃圾收集器紧密耦合。甚至 documentation 也提到了它:

Gets an indication whether the object referenced by the current WeakReference object has been garbage collected.

据我所知,在 C# 中没有办法知道是否仍然存在对给定对象的引用,除非手动浏览整个引用树(并且几乎自己重新实现 GC)。

If I understand correctly after exit the Test1 there would be nothing referenced to the object instance ...

你错了。从技术上讲,您没有任何已创建对象的引用,它 可能 在下一行被 gc。

考虑这个简单的例子:

class Program
{
    static WeakReference _ref;

    static void Main(string[] args)
    {
        Test();
        GC.Collect();
        Console.WriteLine(_ref.IsAlive); // false
    }

    static void Test()
    {
        var obj = new object();
        _ref = new WeakReference(obj);
        GC.Collect();
        Console.WriteLine(_ref.IsAlive); // true
    }
}

Test()中我们有一个对象的强引用,它确实持续到方法结束。

你可以做这样的事情来确定

object obj = _ref.Target;
if (obj != null)
{
    ... safe to do something with obj
}

您无法判断弱引用是否有效。但是你可以判断它是否无效。 我知道这很奇怪。如果我有执行 if 语句的代码并且可以询问 "is it valid" 那么它在下一行代码中可能无效,所以它是无用的。 对 WeakReference 的 TryGetTarget 调用获取对该对象的引用,或者失败并且 returns false。 一旦它有了引用,引用就会阻止对象被垃圾回收,所以至少只要你有引用,它就会保持有效。

在某些代码中,可能会保留一个 List>

跟踪该列表并清除未引用(其他地方)引用的一种好方法是让一些 for 循环使用 TryGetTarget 扫描列表,如果失败,则从列表中删除陈旧的引用。 但是这样的删除会破坏迭代器,因此您想将 RemoveAll 与谓词一起使用。 我有一个名为 CognateBase 的 class 和一个名为 AllCognateBases 的静态全局列表。 CognateBase 有一个 Tick() 函数,我每次在程序滴答时调用它。 Tick 循环是获取陈旧引用的好地方。所以我有...

    public static void TickAll()
    {
        // This will loop through all CognateBase objects and call their Tick, or if deleted from memory, remove the CognateBase.
        AllCognateBases.RemoveAll(_TickIfAble);
    }

然后 _TickIfAble 是

private static bool _TickIfAble(WeakReference<CognateBase> r)
    {
        CognateBase cb;
        if (r.TryGetTarget(out cb))
        {
            cb.Tick();
            return false;
        }
        else
        {
            return true;
        }
    }

因此,有效的 CognateBase 实例会被勾选,不再有效的会被移除。而且因为它在 RemoveAll 中,所以没有迭代器会搞砸。

在我引用 CognateBase 并将其设置为 null 的代码中的任何其他地方,CognateBase 最终将被删除并从列表中移除。

这个测试有效。所有这些 GC 调用都是为了强制垃圾收集立即发生,而不是在 C# 感觉它之后的某个时候发生。

        public static void UnitTest()
    {
        Debug.Assert(AllCognateBases.Count == 0);
        CognateBase b1 = new CognateBase();
        Debug.Assert(AllCognateBases.Count == 1);
        CognateBase b2 = new CognateBase();
        Debug.Assert(AllCognateBases.Count == 2);
        GC.Collect();
        Debug.Assert(AllCognateBases.Count == 2);

        b1 = null;
        GC.Collect();
        GC.WaitForFullGCComplete();
        GC.WaitForPendingFinalizers();
        TickAll();
        GC.Collect();
        GC.WaitForFullGCComplete();
        GC.WaitForPendingFinalizers();
        Debug.Assert(AllCognateBases.Count == 1);

        b2 = null;
        GC.Collect();
        GC.WaitForFullGCComplete();
        GC.WaitForPendingFinalizers();
        TickAll();
        GC.Collect();
        GC.WaitForFullGCComplete();
        GC.WaitForPendingFinalizers();
        Debug.Assert(AllCognateBases.Count == 0);
    }

而创作者...

       public CognateBase()
    {
        AllCognateBases.Add(new WeakReference<CognateBase>(this));
    }

警告 - 当引用设置为 null 时,如上面的 b1 = null;这些对象可能在很长一段时间内都没有被垃圾回收。一直以来它仍然有效并且会得到它的 Tick 调用!