用于多个事件类型的单个事件总线
本文关键字:事件 单个 总线 类型 用于 | 更新日期: 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>
的处理程序。如果不想为每种类型的处理程序定义单独的参数类型,那么除了参数类型之外,还可以包括一个伪类型参数。这种方法会在一定程度上限制可以处理的事件模式,但与普通委托相比,它有一些优势:
- 无论好坏,将一个对象放入订阅者列表将自动附加其所有相关事件。
- 订阅者列表可以将每个订阅者保存为"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);
}