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

С# События утечки памяти

Когда происходит утечка памяти с отмененными подписью? Должен ли я писать деструктор или реализовать IDisposable для отмены подписки на событие?

4b9b3361

Ответ 1

Скажем, что A ссылки B. Кроме того, скажите, что вы думаете, что вы закончили с B и ожидаете, что это будет сбор мусора.

Теперь, если A достижима [1], B не будет собираться мусором, несмотря на то, что "вы сделали с ним". Это, по сути, утечка памяти [2]

Если B подписывается на событие в A, то мы имеем ту же ситуацию: A имеет ссылку на B через делегат обработчика событий.

Итак, когда это проблема? Только когда объект ссылки доступен, как указано выше. В этом случае может быть утечкой, когда экземпляр Foo больше не используется:

class Foo
{
    Bar _bar;

    public Foo(Bar bar)
    {
        _bar = bar;
        _bar.Changed += BarChanged;
    }

    void BarChanged(object sender, EventArgs e) { }
}

Причиной, по которой может быть утечка, является то, что экземпляр Bar, переданный в конструкторе может иметь более длительный срок службы, чем Foo, используя его. Подписанный обработчик событий может, а затем сохранить Foo.

В этом случае вам необходимо предоставить способ отказаться от подписки на событие, чтобы не получить утечку памяти. Один из способов сделать это - позволить Foo реализовать IDisposable. Поверхность этого заключается в том, что он четко сигнализирует потребителю класса, что ему нужно позвонить Dispose(), когда это будет сделано. Другой способ состоит в том, чтобы иметь отдельные методы Subscribe() и Unsubscribe(), но это не передает ожиданий типа - они слишком необязательны для вызова и введения временной связи.

Моя рекомендация:

class sealed Foo : IDisposable
{
    readonly Bar _bar;
    bool _disposed;

    ...

    public void Dispose()
    {
        if (!_disposed)
        {
            _disposed = true;
            _bar.Changed -= BarChanged;
        }
    }

    ...
}

Или, альтернативно:

class sealed Foo : IDisposable
{
    Bar _bar;

    ...

    public void Dispose()
    {
        if (_bar != null)
        {
            _bar.Changed -= BarChanged;
            _bar = null;
        }
    }

    ...
}

С другой стороны, если ссылочный объект не доступен, то не может утечка:

class sealed Foo
{
    Bar _bar;

    public Foo()
    {
        _bar = new Bar();
        _bar.Changed += BarChanged;
    }

    void BarChanged(object sender, EventArgs e) { }
}

В этом случае любой экземпляр Foo всегда будет переживать свой сконфигурированный экземпляр Bar. Если Foo недоступен, то будет Bar. Подписанный обработчик событий не может содержать Foo здесь. Недостатком этого является то, что если Бар - это зависимость, нуждающаяся в издевании в сценариях единичного тестирования, она не может (каким-либо чистым способом) быть явно инсталлирована потребителем, но должна быть введена.

[1] http://msdn.microsoft.com/en-us/magazine/bb985010.aspx

[2] http://en.wikipedia.org/wiki/Memory_leak

Ответ 2

Обработчик событий содержит сильную ссылку на объект, объявляющий обработчик (в свойстве delegate Target).

До тех пор, пока обработчик события не будет удален (или пока объект, владеющий событием, больше не будет ссылаться нигде), объект, содержащий обработчик, не будет собран.

Вы можете исправить это, удалив обработчик, когда он вам больше не нужен (возможно, в Dispose()).
Финализатор не может помочь, потому что финализатор будет работать только после того, как он будет собран.