对象不会被垃圾收集

时间:2011-07-25 14:59:54

标签: c# events garbage-collection destructor

我认为这是一个C#初学者问题,但我似乎无法找到正确的解决方案。

我有一个ClassOne对象,它定义了一个事件。 我创建了一个ClassTwo对象,它被认为是一个黑盒子,这意味着我不知道它是否会注册到任何事件。 ClassTwo构造函数注册到ClassOne的事件。 当ClassTwo对象超出范围时,问题就出现了。垃圾收集器永远不会删除此对象,因为它从未取消注册该事件。

所以我有两个问题:

  1. 当ClassTwo对象超出范围时,有没有办法知道?对于一个旧的C ++程序员来说,这将在析构函数中,但是使用C#这不起作用。

  2. 是否有调试工具可以帮助我找到这样的对象?

  3. 以下是重现问题的示例代码:

        public partial class MainWindow : Window
    {
        static public ClassOne classOne = new ClassOne();
        public MainWindow()
        {
            InitializeComponent();
            ClassTwo classtwo = new ClassTwo();
        }
    
        private void buttonTest_Click(object sender, RoutedEventArgs e)
        {
            GC.Collect();
        }
    }
    public class ClassOne
    {
        public ClassOne()
        {
            Trace.WriteLine(this + " constructor");
        }
    
        ~ClassOne()
        {
            Trace.WriteLine(this + " destructor");
        }
    
        public delegate void UpdateFunc(object sender, EventArgs args);
        public event UpdateFunc OnUpdate;
    
    }
    public class ClassTwo
    {
        public ClassTwo()
        {
            Trace.WriteLine(this + " constructor");
            MainWindow.classOne.OnUpdate += new ClassOne.UpdateFunc(classOne_OnUpdate);
        }
    
        void classOne_OnUpdate(object sender, EventArgs args)
        {
            throw new NotImplementedException();
        }
    
        ~ClassTwo()
        {
            Trace.WriteLine(this + " destructor");
        }
    }
    

3 个答案:

答案 0 :(得分:6)

我会在这样的对象上实现IDisposable,并从Dispose方法中的事件中取消注册。 你会像这样使用你的对象:

using(var two = new ClassTwo(classOne))
{
    // Do something with two
}
// object can now be garbage collected.

如果来电者未能拨打Dispose,那你就不走运了。

答案 1 :(得分:5)

  1. 除非它实现 IDisposable ,否则调用者会通过正确调用Dispose进行合作。 (当然,为什么呼叫者不合作?)

  2. 不是我的意思。 :(我认为你最好的办法是实施IDisposable并在Dispose取消注册。

答案 2 :(得分:0)

正如其他人所提到的,Dispose模式是解决此问题的方法。如果ClassTwo对象存在很短的时间,您可以使用C#using语句来确保在使用完对象后调用Dispose:

using (var foo = new ClassTwo())
{
    foo.Bar();
}

为了找到此类问题的根本原因,您需要使用内存分析器。 dotTrace已被提及,另一个好的是SciTech Memory Profiler。你需要找到的是你认为应该被垃圾收集的对象的根路径,但事实并非如此。根路径是未收集对象的原因 - 因为通过传递引用,保证活着的对象(GC Root)引用您想要死的对象。

这些内存分析器非常有助于识别导致您出现问题的GC根,以及从根到对象的引用路径。沿着该根路径的某个位置将是不合适的引用,并且是该问题的原因。

在您的代码中,未收集ClassTwo对象的原因可能是MainWindow具有对ClassOne对象的静态引用,或者从ClassOne到ClassTwo的事件处理程序尚未取消挂钩。静态引用是GC Roots的一个示例 - 所以在MainWindow中的静态引用发生更改之前,所有classOne引用都将处于活动状态。

静态或事件处理程序是否存在问题取决于应用程序的场景 - 是否也应该收集ClassOne对象 - 是否有静态引用它?或者是静态引用所需的行为 - classOne是一个长期存在的对象,classTwo是一个短命的对象,在这种情况下,classTwo应该在它的生命结束时处置,并且Dispose应该取消挂起事件处理程序。

这是一篇关于.Net GC的好文章,由Jeffrey Richter撰写:http://msdn.microsoft.com/en-us/magazine/bb985010.aspx。它现在有点老了,近年来GC已经有了新增功能,但它是一个很好的起点。