Проблема: зарегистрированные обработчики событий создают ссылку из события на экземпляр обработчика события. Если этот экземпляр не может отменить регистрацию обработчика событий (возможно, через 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.
Я считаю, что это утечка памяти, и похоже на мои старые С++-дни. Я могу, конечно, сделать 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. Класс Bar НЕОБХОДИМО.
Нет необходимости в подклассе WeakEventManager, реализовать IWeakEventListener и т.д.
ОК, так что для реализации WeakReferenceEvent. Это показано здесь. Следует отметить, что он использует общий WeakReference <T> что я заимствовал отсюда: 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() просто пропустите слабые ссылки и вызовите их целевое действие. Если найдены мертвые (т.е. Собранные мусором) ссылки, удалите их из списка.
Конечно, это работает только с делегатами типа Action. Я попытался сделать этот родословный, но столкнулся с отсутствующим, где T: делегировать на С#!
В качестве альтернативы просто измените класс WeakReferenceEvent как WeakReferenceEvent <T> , и замените действие с действием <T> . Исправьте ошибки компилятора, и у вас есть класс, который можно использовать следующим образом:
class Foo
{
public WeakReferenceEvent<int> AnEvent = new WeakReferenceEvent<int>();
internal void DoEvent()
{
AnEvent.Invoke(5);
}
}
Полный код с <T> , и оператор - (для удаления событий) показан здесь:
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);
}
}
Надеюсь, это поможет кому-то еще, когда они столкнутся с загадочным событием, вызвавшим утечку памяти в собранном мусором мире!