Объект не получает мусор - PullRequest
13 голосов
/ 25 июля 2011

Я думаю, что это вопрос новичка в C #, но я не могу найти правильное решение.

У меня есть объект ClassOne, который определяет событие. Я создаю объект ClassTwo, который рассматривается как черный ящик, что означает, что я не знаю, будет ли он регистрироваться на какое-либо событие или нет. Конструктор ClassTwo регистрируется на событие ClassOne. Проблема возникает, когда объект ClassTwo выходит из области видимости. Сборщик мусора никогда не удаляет этот объект, потому что он никогда не отменял регистрацию события.

Итак, у меня два вопроса:

  1. Есть ли способ для объекта ClassTwo узнать, когда он выходит из области видимости? Для старого программиста C ++ это было бы в деструкторе, но в C # это не работает.

  2. Существует ли инструмент отладки, который помогает мне находить такие объекты?

Вот пример кода для воспроизведения проблемы:

    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 ]

6 голосов
/ 25 июля 2011

Я бы реализовал IDisposable для такого объекта и отменил бы регистрацию события в методе Dispose.Вы бы использовали свой объект следующим образом:

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

Если звонящий не может позвонить Dispose, вам не повезло.

5 голосов
/ 25 июля 2011
  1. Нет, если только он не реализует IDisposable и , вызывающий абонент взаимодействует, правильно вызывая Dispose(Конечно, почему звонящий не будет сотрудничать?)

  2. Не то чтобы я о.:( Я думаю, что вам лучше всего внедрить IDisposable и отменить регистрацию на Dispose.

0 голосов
/ 27 июля 2011

Как уже упоминали другие, шаблон Dispose является способом решения этой проблемы. Если объект ClassTwo живет в течение короткого времени, вы можете использовать оператор C # using, чтобы гарантировать, что Dispose вызывается после того, как вы закончили использовать объект:

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

Чтобы найти причину таких проблем, вам нужно использовать профилировщик памяти. dotTrace уже упоминался, и еще одним хорошим примером является SciTech Memory Profiler . Что вам нужно найти, так это корневой путь к объекту, который, по вашему мнению, следует собирать, но это не так. Корневые пути - это причина, по которой объект не собирается - потому что через транзитивные ссылки объект, который гарантированно будет живым (корень GC), ссылается на ваш объект, который вы хотите убить.

Эти профилировщики памяти очень полезны для определения того, какие корни GC вызывают у вас проблемы, и каковы пути ссылки от корня к вашему объекту. Где-то вдоль этого корневого пути будет ссылка, которая неуместна и является причиной проблемы.

В вашем коде причиной того, что объект ClassTwo не был собран, может быть либо тот факт, что MainWindow имеет статическую ссылку на объект ClassOne, либо то, что обработчик события от ClassOne до ClassTwo не был отцеплен. Статические ссылки являются одним примером GC Roots - поэтому все ссылки classOne будут живы, пока эта статическая ссылка в MainWindow не будет изменена.

Является ли проблема статическим или обработчиком событий, зависит от сценария вашего приложения - должен ли объект ClassOne также быть собран - неправильно ли иметь статическую ссылку на него? Или желаемое поведение статической ссылки - это classOne долгоживущий объект, а classTwo недолговечный, и в этом случае classTwo должен быть Disposed по окончании его жизни, а Dispose должен отцепить обработчик события.

Это хорошая статья для изучения .Net GC, написанная Джеффри Рихтером: http://msdn.microsoft.com/en-us/magazine/bb985010.aspx. Сейчас она немного устарела, в последние годы появились новые дополнения к GC, но это отличное место для начала.

...