Используя WeakEventManager, скрывая фактическое событие - вновь - PullRequest
0 голосов
/ 02 мая 2020

У меня есть событие, на которое следует подписаться только с использованием WeakEventManager (для моделей представления, которые не могут отменить регистрацию, поскольку у них нет надежного механизма выгрузки / удаления), и я хотел бы скрыть исходное событие, чтобы другие пользователи не могли его использовать +=.

Одна проблема заключается в том, что событие, на которое подписывается WeakEventManager, должно быть опубликовано c, то есть не скрыто . Этот пост ( Использование WeakEventManager, скрывающее фактическое событие ) имеет интересное решение, в котором событие заключено в закрытый класс (с событием publi c).

Я принял это решение и придумал следующий вспомогательный класс:

public class WeakEvent<TEventArgs> where TEventArgs : EventArgs
{
    private class InternalWrapper
    {
        public event EventHandler<TEventArgs> InternalEvent;
        public void Invoke(object sender, TEventArgs args) => InternalEvent?.Invoke(sender, args);
    }

    private InternalWrapper _wrapper = new InternalWrapper();

    public void Subscribe(EventHandler<TEventArgs> handler)
    {
        WeakEventManager<InternalWrapper, TEventArgs>.AddHandler(_wrapper, nameof(InternalWrapper.InternalEvent), handler);
    }

    public void Invoke(object sender, TEventArgs args) => _wrapper.Invoke(sender, args);
}

Моя проблема в том, что события могут быть запущены, но в InternalWrapper.Invoke InternalEvent всегда есть список вызовов null - как будто регистрация, созданная WeakEventManager, была собрана сборщиком мусора, хотя handler - это метод очень живого экземпляра.

Может ли это быть дженерики?

1 Ответ

0 голосов
/ 04 мая 2020

Ответ: нет, это не дженерики.

Обработчики событий, зарегистрированные с использованием WeakEventManager, запускаются только в том случае, если source, указанное при регистрации, совпадает с тем, которое использовалось для вызова события, то есть оно должно вызываться (из source) использование InternalEvent.Invoke(**this**, args).

Одним из следствий этого является то, что отправитель всегда будет экземпляром закрытого внутреннего класса, который практически бесполезен для любого потребляющего кода.

Для моей окончательной реализации ниже Я добавил перегрузку Subscribe(Action<TEventArgs> action), чтобы поддерживать чистоту кода, если это возможно. (Метод возвращает EventHandler<TEventArgs>, который может использоваться для вызова Unsubscribe, если требуется.)

РЕДАКТИРОВАТЬ: При использовании перегрузки Action, если ссылка на возвращено EventHandler не сохраняется, сборщик мусора - рано или поздно - соберет регистрацию, и действие сработает, как и ожидалось.

Еще одна вещь, которую следует учитывать, - это то, что событие может быть инициировано / вызвано из любого кода используя public void Invoke, но я все равно часто нуждаюсь в этом методе и реализую его.

Я считаю этот код намного более простым и простым в использовании, чем "нативная" реализация WeakEventManager - и, что самое важное он предотвращает утечку памяти из-за использования кода += для прослушивания события.

public class WeakEvent<TEventArgs> where TEventArgs : EventArgs
{
    private class WeakEventInternal
    {
        public event EventHandler<TEventArgs> InternalEvent;
        public void Invoke(TEventArgs args) => InternalEvent?.Invoke(this, args);
    }

    private readonly WeakEventInternal _internal= new WeakEventInternal();

    public void Subscribe(EventHandler<TEventArgs> handler) => WeakEventManager<WeakEventInternal, TEventArgs>.AddHandler(_internal, nameof(WeakEventInternal.InternalEvent), handler);

    public void Unsubscribe(EventHandler<TEventArgs> handler) => WeakEventManager<WeakEventInternal, TEventArgs>.RemoveHandler(_internal, nameof(WeakEventInternal.InternalEvent), handler);

    public EventHandler<TEventArgs> Subscribe(Action<TEventArgs> handler)
    {
        EventHandler<TEventArgs> internalHandler = (object s, TEventArgs ee) => handler(ee);

        WeakEventManager<WeakEventInternal, TEventArgs>.AddHandler(_internal, nameof(WeakEventInternal.InternalEvent), internalHandler);

        return internalHandler;
    }

    public void Invoke(TEventArgs args) => _internal.Invoke(args);
}
...