事件总线是对发布-订阅模式的一种实现,是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。
什么是“总线”:一个集中式的事件处理机制。同时服务多个事件和多个观察者。相当于一个介于Publisher和Subscriber中间的桥梁。它隔离了Publlisher和Subscriber之间的直接依赖,接管了所有事件的发布和订阅逻辑,并负责事件的中转。
(图片源自:事件总线知多少(1) - 简书)
实现方式:
1,EventBus维护一个事件类型的字典,发布者、订阅者在事件总线中获取此字典并执行发布、订阅操作(维护一个事件源与事件处理的映射字典)。
2,通过单例模式,确保事件总线的唯一入口
3,提供统一的事件注册、取消注册和触发(发布)方法
代码实现:
发布,订阅都是基于事件。首先我们定义事件接口,泛型接口(规定了事件参数类型)和具体的事件。
///
/// 事件源
/// 所有被订阅的事件都要继承这个
///
public abstract class IEvent
{
///
///
///
public string EventName;
}
public abstract class IEvent: IEvent where T : MyEventArgs
{
}
public class EventA :IEvent
{
}
public class EventB : IEvent
{
}
事件参数
public class MyEventArgs:EventArgs
{
///
/// 事件发生的时间
///
public DateTime DateTime { get; set; }
}
///
/// 事件A的事件参数
///
public class MyEventArgsA:MyEventArgs
{
}
///
/// 事件B的事件参数
///
public class MyEventArgsB:MyEventArgs
{
}
发布者:
不一定要特别定义一个Publisher。任何人任何时候都可以发布一个事件。只要发生事件并且发布到事件总线中即可。EventB就没有发布者,直接在program中发布 。
public interface IPublisher where T:IEvent
{
///
/// 发布事件
///
void Pub();
}
///
/// 发布者
///
///
public class PublisherA : IPublisher
{
///
/// 发布事件
///
public void Pub()
{
//创建事件并发布
EventBus.Default.Publish(new EventA() { EventName = "我是EventA" }
, this
, new MyEventArgsA() { DateTime = DateTime.Now });
}
}
订阅者:
///
/// 观察者(订阅者)接口
/// 规范了订阅的事件类型
///
public abstract class ISubscriber
{
public abstract void Subscribe(Action
终于到事件总线了。
public class EventBus
{
private static EventBus _default;
protected static readonly object locker = new object();
private Dictionary>> eventDic = new Dictionary>>();
public static EventBus Default
{
get
{
if (_default == null)
{
lock (locker)
{
// 如果类的实例不存在则创建,否则直接返回
if (_default == null)
{
_default = new EventBus();
}
}
}
return _default;
}
}
///
///
///
/// 订阅的时候,就应该知道订阅什么事件
///
public void Subscribe( Action eventHandler) where T:IEvent
{
lock (locker)
{
if (eventDic.ContainsKey(typeof(T)))
{
eventDic[typeof(T)].Add(eventHandler);
}
else
{
eventDic.Add(typeof(T), new List>() { eventHandler });
}
}
}
public void Unsubscribe(Action eventHandler) where T : IEvent
{
lock (locker)
{
if (eventDic.ContainsKey(typeof(T)) && eventDic[typeof(T)].Contains(eventHandler))
{
eventDic[typeof(T)].Remove(eventHandler);
}
}
}
///
/// 发布
///
/// 发布的时候,应该是发布具体的事件,而不是事件类型
///
///
public virtual void Publish(IEvent e,object sender, MyEventArgs eventArgs)
{
lock (locker)
{
if (eventDic.ContainsKey(e.GetType()))
{
var subscriptions = eventDic[e.GetType()];
for (int i = 0; i < subscriptions.Count; i++)
{
subscriptions[i](sender, eventArgs);
}
}
}
}
}
eventDic就是上面说的维护的事件类型字典。记录了每个事件类型对应的要执行的所有Action。
EventBus全局唯一实例,这里使用Default实例。
有订阅,取消订阅,发布方法。也有用反射自动实现订阅的。
最后就是调用
class Program
{
static void Main(string[] args)
{
SubscriberA suba = new SubscriberA();
suba.Subscribe();
PublisherA puba = new PublisherA();
puba.Pub();
SubscriberB subb = new SubscriberB();
subb.Subscribe();
//创建并发布事件
EventBus.Default.Publish(new EventB() { EventName = "我是EventB" }
, "Program"
, new MyEventArgsA() { DateTime = DateTime.Now });
}
}
输出
ScbscriberA Handler sender:MyEventBus.PublisherA eventArgs DateTime:2022/11/19 10:01:00
ScbscriberB Handler sender:Program eventArgs DateTime:2022/11/19 10:01:00
比较好的讲事件总线的文章
什么是事件总线 - 走看看
详解c# 事件总线
过程中遇到一个问题:
类型M继承于N,但是Action
原因如下
例如
Action
Console.WriteLine(x.Length);
};
Action
b(new object())//无法执行,object无Length属性