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