Проблема: зарегистрированные обработчики событий создают ссылку из события на экземпляр обработчика события. Если этому экземпляру не удается отменить регистрацию обработчика событий (предположительно, через Dispose), сборщик мусора не освободит память экземпляра.
Пример:
class Foo
{
public event Action AnEvent;
public void DoEvent()
{
if (AnEvent != null)
AnEvent();
}
}
class Bar
{
public Bar(Foo l)
{
l.AnEvent += l_AnEvent;
}
void l_AnEvent()
{
}
}
Если я создаю экземпляр Foo и передаю его новому конструктору Bar, а затем отпускаю объект Bar, он не будет освобожден сборщиком мусора из-за регистрации AnEvent.
Я считаю это утечкой памяти, и похоже на мои старые C ++ дни. Конечно, я могу сделать Bar IDisposable, отменить регистрацию события в методе Dispose () и обязательно вызвать Dispose () для его экземпляров, но почему я должен это делать?
У меня первый вопрос, почему события реализуются с сильными ссылками? Почему бы не использовать слабые ссылки? Событие используется для абстрактного уведомления объекта об изменениях в другом объекте. Мне кажется, что если экземпляр обработчика событий больше не используется (т. Е. Нет ссылок на не-события на объект), то любые события, с которыми он зарегистрирован, должны автоматически быть незарегистрированными. Чего мне не хватает?
Я посмотрел на WeakEventManager. Вау, какая боль. Мало того, что его очень сложно использовать, но его документация неадекватна (см. http://msdn.microsoft.com/en-us/library/system.windows.weakeventmanager.aspx - обратите внимание на раздел «Примечания для наследников», в котором есть 6 неопределенно описанных маркеров).
Я видел другие дискуссии в разных местах, но ничего, что я чувствовал, я не мог использовать. Я предлагаю более простое решение на основе WeakReference, как описано здесь. Мой вопрос: не соответствует ли это требованиям значительно меньшей сложности?
Для использования решения приведенный выше код изменяется следующим образом:
class Foo
{
public WeakReferenceEvent AnEvent = new WeakReferenceEvent();
internal void DoEvent()
{
AnEvent.Invoke();
}
}
class Bar
{
public Bar(Foo l)
{
l.AnEvent += l_AnEvent;
}
void l_AnEvent()
{
}
}
Обратите внимание на две вещи:
1. Класс Foo изменяется двумя способами: событие заменяется экземпляром WeakReferenceEvent, показанным ниже; и вызов события изменяется.
2. Класс бара НЕИЗМЕНЕН.
Нет необходимости создавать подклассы WeakEventManager, реализовывать IWeakEventListener и т. Д.
ОК, так что до реализации WeakReferenceEvent. Это показано здесь. Обратите внимание, что он использует общий WeakReference , который я позаимствовал здесь: http://damieng.com/blog/2006/08/01/implementingweakreferencet
class WeakReferenceEvent
{
public static WeakReferenceEvent operator +(WeakReferenceEvent wre, Action handler)
{
wre._delegates.Add(new WeakReference<Action>(handler));
return wre;
}
List<WeakReference<Action>> _delegates = new List<WeakReference<Action>>();
internal void Invoke()
{
List<WeakReference<Action>> toRemove = null;
foreach (var del in _delegates)
{
if (del.IsAlive)
del.Target();
else
{
if (toRemove == null)
toRemove = new List<WeakReference<Action>>();
toRemove.Add(del);
}
}
if (toRemove != null)
foreach (var del in toRemove)
_delegates.Remove(del);
}
}
Функциональность тривиальна. Я переопределяю оператор +, чтобы получить + = синтаксические события соответствия сахара. Это создает WeakReferences для делегата Action. Это позволяет сборщику мусора освободить целевой объект события (в данном примере Bar), когда никто больше не удерживает его.
В методе Invoke () просто пропустите слабые ссылки и вызовите их Target Action. Если найдены какие-либо мертвые (то есть, собранные мусор) ссылки, удалите их из списка.
Конечно, это работает только с делегатами типа Action. Я попытался сделать этот универсальный, но столкнулся с отсутствующим, где T: делегат в C #!
В качестве альтернативы просто измените класс WeakReferenceEvent на WeakReferenceEvent и замените Action на Action . Исправьте ошибки компилятора, и у вас есть класс, который можно использовать так:
class Foo
{
public WeakReferenceEvent<int> AnEvent = new WeakReferenceEvent<int>();
internal void DoEvent()
{
AnEvent.Invoke(5);
}
}
Полный код с и оператором (для удаления событий) показан здесь:
class WeakReferenceEvent<T>
{
public static WeakReferenceEvent<T> operator +(WeakReferenceEvent<T> wre, Action<T> handler)
{
wre.Add(handler);
return wre;
}
private void Add(Action<T> handler)
{
foreach (var del in _delegates)
if (del.Target == handler)
return;
_delegates.Add(new WeakReference<Action<T>>(handler));
}
public static WeakReferenceEvent<T> operator -(WeakReferenceEvent<T> wre, Action<T> handler)
{
wre.Remove(handler);
return wre;
}
private void Remove(Action<T> handler)
{
foreach (var del in _delegates)
if (del.Target == handler)
{
_delegates.Remove(del);
return;
}
}
List<WeakReference<Action<T>>> _delegates = new List<WeakReference<Action<T>>>();
internal void Invoke(T arg)
{
List<WeakReference<Action<T>>> toRemove = null;
foreach (var del in _delegates)
{
if (del.IsAlive)
del.Target(arg);
else
{
if (toRemove == null)
toRemove = new List<WeakReference<Action<T>>>();
toRemove.Add(del);
}
}
if (toRemove != null)
foreach (var del in toRemove)
_delegates.Remove(del);
}
}
Надеюсь, это поможет кому-то другому, когда они столкнутся с загадочным событием, вызвавшим утечку памяти в мире, где собирается мусор!