C#:收集WeakReference之前的通知?

时间:2009-07-17 20:29:45

标签: c# .net events garbage-collection weak-references

在C#/ .NET中,有没有办法在弱引用指向的对象被破坏之前获得通知?基本上,我想允许收集一个对象,但是在对象被销毁之前做一些事情,而不修改代码来添加析构函数(因为我不知道我的代码将使用什么类型的对象)。

谢谢, 罗伯特

6 个答案:

答案 0 :(得分:7)

.Net 4.0提供了您需要的解决方案:ConditionalWeakTable。这是一个简短的程序,展示了这个想法。 (还讨论了here

using System;
using System.Runtime.CompilerServices;

namespace GCCollectNotification
{
    class ObjectToWatch { }

    class Notifier
    {
        public object ObjectToWatch { get; set; }
        ~Notifier() { Console.WriteLine("object is collected"); }
    }

    class Program
    {
        private ConditionalWeakTable<object, Notifier> map
            = new ConditionalWeakTable<object, Notifier>();

        public void Test()
        {
            var obj = new ObjectToWatch();
            var notifier = map.GetOrCreateValue(obj);
            notifier.ObjectToWatch = obj;
        }

        static void Main(string[] args)
        {
            new Program().Test();

            GC.Collect();
            GC.WaitForPendingFinalizers();

            // "object is collected" should have been printed by now

            Console.WriteLine("end of program");
        }
    }
}

答案 1 :(得分:6)

没有办法实现此功能。

经过一番猜测后,我认为不可能以你描述的方式实现一个功能。

考虑到收集WeakReference持有的对象时,没有更多的引用(因此它是可收集的)。对于对您有用的事件,它需要提供对象作为事件的一部分。这意味着引用已从可收集到不可收集。没有什么能阻止处理代码重新获得对该对象的引用。因此,该对象不再被视为可收集。 CLR需要在对象上进行第二次传递以重新确保它是可收集的。

您可以看到第二次无法引发事件,因为它会导致无法收集的对象。

如果声称此事件是在收集对象之前引发的,则会误用命名。只是因为任何处理程序都可以通过建立对对象的新引用来阻止收集它。相反,它必须是“ObjectMaybeAboutToBeCollected”。这可能不会给你你正在寻找的行为。

答案 2 :(得分:6)

你做不到。 但是,您可以做的是观察GC何时接近(CLR v3.5Sp1中有新的GC API允许您这样做,GCNotifications)

答案 3 :(得分:0)

你的问题对我没有意义。将被称为驻留的代码在哪里?鉴于在引用的对象被销毁之前弱引用将被清零,因此它成为引用即将被销毁的对象的类的一部分是没有意义的。并且在对象被销毁之前已经调用了引用对象中的代码 - 这就是析构函数。

您想要解决的实际设计问题是什么?可能有更好的方法。

答案 4 :(得分:0)

对于你所描述的内容,终结器将是一种更好的方法。

答案 5 :(得分:0)

如果带有通知程序的弱引用被视为与具有终结符的对象类似,则可能具有与您描述的语义类似的语义,也就是说当对象被认为不再对任何人感兴趣时,它将排队等待最终确定和通知;队列条目将被视为实时引用,因此在对象被采取行动之前,实际上不会收集该对象。

由于这是不可能的,最好的可行方法可能是让所有“我对这个对象感兴趣”引用指向一个轻量级的包装器对象,它反过来指向真实对象,并具有“弱“引用指向一个不同的包装器,它也指向真实的对象。第一个包装器应该保持对第二个的引用,但反之则不然。第一个包装器应该有一个终结器,当它超出范围时会触发适当的代码。

不幸的是,我还没有看到这种策略的任何完整实现。需要考虑一些重要的警告。其中:(1)终结者不应该等待锁定,也不应该做任何可能引发异常的事情; (2)访问可能已超出范围的其他对象的代码必须准备好它们可能已经最终确定,正在最终确定,等待最终确定或在其他地方仍有实时参考的可能性; (3)如果终结器存储对已被发现有资格进行垃圾收集的可终结对象的有根引用,则即使存在实时引用,也可以最终确定此对象。