Подтвердить что ты не робот

Объект не получает сбор мусора

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

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

У меня есть два вопроса:

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

  • Есть ли инструмент отладки, который помогает мне находить такие объекты?

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

    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");
    }
}
4b9b3361

Ответ 1

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

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

Если вызывающий абонент не вызвал Dispose, вам не повезло.

Ответ 2

  • Если он не реализует IDisposable и, вызывающий взаимодействует, вызывая Dispose правильно. (Конечно, почему бы не позвонить сотруднику?)

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

Ответ 3

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

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

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

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

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

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

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