用于多个事件类型的单个事件总线

本文关键字:事件 单个 总线 类型 用于 | 更新日期: 2023-09-27 18:26:47

我设置了一个事件总线,它只为所有事件类型传递一个字符串。这很好,但现在我希望每个事件类型都有不同的事件参数。我看不出有什么方法可以保留一个单独的订阅者集合,这些订阅者都有不同的事件参数。我可以使用基类型作为事件参数,但随后事件处理程序被迫使用基类型,订阅者必须将事件参数强制转换为具体类型(我不希望)。我基本上有这样的东西:

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;
        }

除了为每种事件类型保留一些不同列表的事件字典之外,我不确定该如何处理。我知道有第三方库,但我更愿意自己编写代码。我也研究过类似的问题,但没有发现任何问题。如果有人对如何解决这个问题有建议或其他建议,我们将不胜感激。

用于多个事件类型的单个事件总线

如果添加另一个泛型参数,则可以生成强类型事件。

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个通用参数调用subscribe和publish会增加开销,但另一方面,您可以将事件绑定到特定的eventArgs,并且使用者不能为给定事件传递任何任意的eventArg。他们需要匹配。

这里有一个小的优化。与其创建自己的操作列表,不如将操作相加,并允许多播代理为您跟踪所有操作。例如:

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个类型参数,用于将所订阅的方法强制转换为Action,因为编译器无法仅从方法签名推断出情况

考虑到这一点,这里有另一种方法,但您不能避免指定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);

可以用泛型接口做一些有趣的事情,而这是委托无法完成的。如果每个使用"事件"的类只需要为每种类型的参数有一个处理程序,那么这里可能可行的一种方法是用方法InvokeEvent(T param)定义一个接口IKingEventHandler<T>,并有一个方法RaiseKingEvent<TT>(TT param),该方法搜索订阅的处理程序对象列表并调用任何实现IKingEventHandler<TT>的处理程序。如果不想为每种类型的处理程序定义单独的参数类型,那么除了参数类型之外,还可以包括一个伪类型参数。这种方法会在一定程度上限制可以处理的事件模式,但与普通委托相比,它有一些优势:

  1. 无论好坏,将一个对象放入订阅者列表将自动附加其所有相关事件。
  2. 订阅者列表可以将每个订阅者保存为"WeakReference",从而避免了传统上困扰事件发布者的内存泄漏问题。

这并不是接口能做的委托不能做的最有趣的事情(接口支持开放泛型方法的能力要有趣得多),但在某些情况下,这可能是一种有用的模式。

我使用类似的方法来引发域事件。以下是基本想法(更改了代码,使其未经测试):

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);
}