Шина с одним событием для нескольких типов событий - PullRequest
1 голос
/ 06 марта 2012

Я установил шину событий, которая просто передает строку для всех типов событий.Это работало хорошо, но теперь я хочу разные аргументы события для каждого типа события.Я не вижу способа сохранить единую коллекцию подписчиков с разными аргументами событий.Я могу использовать базовый тип для аргументов события, но затем обработчики событий вынуждены использовать базовый тип, и подписчики должны приводить аргументы события к конкретному типу (что мне не нужно).В основном у меня есть что-то вроде этого:

public abstract class PresentationEvent

{
    private readonly List<Action<IPresentationEventArgs>> _subscribers = new List<Action<IPresentationEventArgs>>();

    public void Subscribe(Action<IPresentationEventArgs> action)
    {
        _subscribers.Add(action);
    }

    public void Publish(IPresentationEventArgs message)
    {
        foreach (var sub in _subscribers)
        {
            sub.Invoke(message);
        }
    }
}

 public class MessageChangedEvent : PresentationEvent
    {

    }

public static class EventBus 
    {
        private static readonly Dictionary<Type, PresentationEvent> _mapping = new Dictionary<Type, PresentationEvent>();

        private static PresentationEvent GetPresentationEvent<T>() where T : PresentationEvent, new()
        {
            if (_mapping.ContainsKey(typeof(T)))
            {
                return _mapping[typeof(T)];
            }

            var presEvent = new T();
            _mapping.Add(typeof(T), presEvent);

            return presEvent;
        }

        public static void Subscribe<T>(Action<IPresentationEventArgs> action) where T: PresentationEvent, new()
        {
            var presEvent = GetPresentationEvent<T>();
            presEvent.Subscribe(action);
        }

        public static void Publish<T>(IPresentationEventArgs args) where T : PresentationEvent, new()
        {
            var presEvent = GetPresentationEvent<T>();
            presEvent.Publish(args);
        }
    }

Но при обработке события я вынужден сделать это:

private void OnMessageChanged(IPresentationEventArgs x)
        {
// do cast here
        }

вместо:

 private void OnMessageChanged(MessageChangedEventArgs args)
        {
            label1.Text = args.Message;
        }

Помимо хранения какого-либо словаря событий с различными списками для каждого типа событий, я не уверен, как бы я справился с этим.Я знаю, что есть сторонние библиотеки, но я бы предпочел написать код сам.Я также смотрел на подобные вопросы и ничего не нашел.Если у кого-то есть предложения по решению этой проблемы или другие рекомендации, мы будем благодарны.

Ответы [ 3 ]

1 голос
/ 06 марта 2012

Я использую нечто подобное для возбуждения событий домена.Вот основная идея (изменил код, чтобы он не тестировался):

public static class EventBus
{
    private static List<Delegate> actions;

    public static void Register<T>(Action<T> callback) where T : IPresentationEvent
    {
        if (actions == null)
        {
            actions = new List<Delegate>();
        }

        actions.Add(callback);
    }

    public static void ClearCallbacks()
    {
        actions = null;
    }

    public static void Raise<T>(T args) where T : IPresentationEvent
    {
        if (actions == null)
        {
           return;
        }

        foreach (var action in actions)
        {
            if (!(action is Action<T>))
            {
                continue;
            }

            ((Action<T>)action).Invoke(args);
        }
    }
}

Обновление :

У меня есть интерфейс маркера:

public interface IPresentationEvent
{
}

И обработчики выглядят так:

public interface IHandlePresentationEvent<T> where T : IPresentationEvent
{
    void Handle(T args);
}
1 голос
/ 07 марта 2012

Если вы добавите еще один универсальный параметр, вы можете иметь строго типизированные события.

public interface IPresentationEventArgs { }

public abstract class PresentationEvent<TPresentationEventArgs> where TPresentationEventArgs : IPresentationEventArgs
{
    private readonly List<Action<TPresentationEventArgs>> _subscribers = new List<Action<TPresentationEventArgs>>();

    public void Subscribe(Action<TPresentationEventArgs> action)
    {
        _subscribers.Add(action);
    }

    public void Publish(TPresentationEventArgs message)
    {
        foreach (var sub in _subscribers)
        {
            sub.Invoke(message);
        }
    }
}

public class MessageChangedEventArgs : IPresentationEventArgs 
{
    public string Message { get; set; }
}

public class MessageChangedEvent : PresentationEvent<MessageChangedEventArgs>
{

}

public static class EventBus
{
    private static readonly Dictionary<Type, Func<Object>> _mapping = new Dictionary<Type, Func<Object>>();

    private static T GetPresentationEvent<T, TArgs>()
        where T : PresentationEvent<TArgs>, new()
        where TArgs : IPresentationEventArgs
    {
        if (_mapping.ContainsKey(typeof(T)))
        {
            return _mapping[typeof(T)]() as T;
        }

        var presEvent = new T();
        _mapping.Add(typeof(T), () => presEvent);

        return presEvent;
    }

    public static void Subscribe<T, TArgs>(Action<TArgs> action) where T : PresentationEvent<TArgs>, new()
        where TArgs : IPresentationEventArgs
    {
        var presEvent = GetPresentationEvent<T, TArgs>();
        presEvent.Subscribe(action);
    }

    public static void Publish<T, TArgs>(TArgs args) where T : PresentationEvent<TArgs>, new()
        where TArgs : IPresentationEventArgs
    {
        var presEvent = GetPresentationEvent<T, TArgs>();
        presEvent.Publish(args);
    }
}

Итак, небольшая тестовая программа для демонстрации того, как это может работать:

class Program
{
    static void OnMessageChanged(MessageChangedEventArgs args)
    {
        Console.WriteLine(args.Message);
    }

    static void Main(string[] args)
    {
        EventBus.Subscribe<MessageChangedEvent, MessageChangedEventArgs>(OnMessageChanged);
        EventBus.Publish<MessageChangedEvent, MessageChangedEventArgs>(new MessageChangedEventArgs{ Message = "Hello world."});

        Console.ReadKey();
    }
}

У вас есть дополнительные накладные расходы на вызов подписки и публикации с 2 общими параметрами, но с другой стороныВы можете привязать событие к определенному EventArgs, и потребители не могут передавать произвольные EventArgs для данного события.Им нужно будет соответствовать.

Вот небольшая оптимизация.Вместо того чтобы создавать свой собственный список действий, вы можете просто добавить действия и позволить делегату многоадресной рассылки отслеживать все действия за вас.Например:

public abstract class PresentationEvent<TPresentationEventArgs> where TPresentationEventArgs : IPresentationEventArgs
{
    private Action<TPresentationEventArgs> _actions = args => { };

    public void Subscribe(Action<TPresentationEventArgs> action)
    {
        _actions += action;
    }

    public void Publish(TPresentationEventArgs message)
    {
        _actions(message);
    }
}

Обновление

Вот еще один способ подписки.Но независимо от того, какой подход вы выберете, если вам нужны статические ссылки и проверки времени компиляции, вам нужно будет предоставить аргументы 2 типов.

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

Имея это в виду, здесь есть другой способ, но вам не нужно указывать 2 аргумента.

public static class IPresentationEventArgsExtensions
{
    public static void SubscribeTo<TEvent, TArgs>(this TEvent target, Action<TArgs> action)
        where TArgs : IPresentationEventArgs
        where TEvent : PresentationEvent<TArgs>, new()
    {
        EventBus.Subscribe<TEvent, TArgs>(action);
    }
}

// Use
 Action<MessageChangedEventArgs> messageChangedMethod = OnMessageChanged; // The compiler cannot infer that OnMessageChanged is a Action<IPresentationEventArgs>
 new MessageChangedEvent().SubscribeTo(messageChangedMethod);
1 голос
/ 06 марта 2012

Можно сделать некоторые интересные вещи с универсальными интерфейсами, которые нельзя сделать с делегатами.Один из подходов, который может быть здесь применим, если каждый класс, использующий ваши «события», должен иметь только один обработчик для каждого типа параметра, заключается в определении интерфейса IKingEventHandler<T> с методом InvokeEvent(T param) и метода RaiseKingEvent<TT>(TT param), который просматривает список подписанных объектов-обработчиков и вызывает любой, который реализует IKingEventHandler<TT>.Если не требуется определять отдельные типы параметров для каждого типа обработчика, можно добавить параметр типа «фиктивный» в дополнение к типу параметра.Этот подход несколько ограничил бы шаблоны событий, которые можно обрабатывать, но у него было бы несколько преимуществ перед обычными делегатами:

  1. К лучшему или к худшему, включение объекта в список подписчиков автоматически привело бы квсе связанные с ним события.
  2. Список подписчиков может содержать каждого подписчика как «WeakReference», что позволяет избежать проблем с утечкой памяти, которые традиционно преследуют издателей событий.

Это едва ли не самая интересная вещь, которую могут сделать интерфейсы, которую делегаты не могут сделать (способность интерфейсов поддерживать открытые универсальные методы гораздо интереснее), но, тем не менее, она может быть полезной моделью в некоторых ситуациях.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...