C#事件总线
简介
事件总线是对发布-订阅模式的一种实现,是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。
实现事件总线
EventBus维护一个事件的字典,发布者、订阅者在事件总线中获取事件实例并执行发布、订阅操作,事件实例负责维护、执行事件处理程序。流程如下:

定义事件基类
事件实例需要在事件总线中注册,定义一个基类方便事件总线进行管理,代码如下:
/// <summary>
/// 事件基类
/// </summary>
public abstract class EventBase{ }
事件实例需要管理、执行已经注册的事件处理程序,为了适应不同的事件参数使用泛型参数,不允许此类实例化。代码如下:
/// <summary>
/// 泛型事件
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class PubSubEvent<T> : EventBase where T : EventArgs
{
protected static readonly object locker = new object();
protected readonly List<Action<object, T>> subscriptions = new List<Action<object, T>>();
public void Subscribe(Action<object, T> eventHandler)
{
lock (locker)
{
if (!subscriptions.Contains(eventHandler))
{
subscriptions.Add(eventHandler);
}
}
}
public void Unsubscribe(Action<object, T> eventHandler)
{
lock (locker)
{
if (subscriptions.Contains(eventHandler))
{
subscriptions.Remove(eventHandler);
}
}
}
public virtual void Publish(object sender, T eventArgs)
{
lock (locker)
{
for (int i = 0; i < subscriptions.Count; i++)
{
subscriptions[i](sender, eventArgs);
}
}
}
}
定义事件参数基类
事件参数基类继承EventArgs,使用泛型参数适应不同的参数类型,不允许此类实例化。代码如下:
/// <summary>
/// 泛型事件参数
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class PubSubEventArgs<T> : EventArgs
{
public T Value { get; set; }
}
定义EventBus
EventBus只提供事件实例的管理,具体事件处理程序的执行由事件实例自己负责。为了使用方便,构造函数有自动注册事件的功能,在有多个程序集时可能会有bug。代码如下:
/// <summary>
/// 事件总线
/// </summary>
class EventBus
{
private static EventBus _default;
private static readonly object locker = new object();
private Dictionary<Type, EventBase> eventDic = new Dictionary<Type, EventBase>();
/// <summary>
/// 默认事件总线实例,建议只使用此实例
/// </summary>
public static EventBus Default
{
get
{
if (_default == null)
{
lock (locker)
{
// 如果类的实例不存在则创建,否则直接返回
if (_default == null)
{
_default = new EventBus();
}
}
}
return _default;
}
}
/// <summary>
/// 构造函数,自动加载EventBase的派生类实现
/// </summary>
public EventBus()
{
Type type = typeof(EventBase);
Type typePubSub = typeof(PubSubEvent<>);
Assembly assembly = Assembly.GetAssembly(type);
List<Type> typeList = assembly.GetTypes()
.Where(t => t != type && t != typePubSub && type.IsAssignableFrom(t))
.ToList();
foreach (var item in typeList)
{
EventBase eventBase = (EventBase)assembly.CreateInstance(item.FullName);
eventDic.Add(item, eventBase);
}
}
/// <summary>
/// 获取事件实例
/// </summary>
/// <typeparam name="TEvent">事件类型</typeparam>
/// <returns></returns>
public TEvent GetEvent<TEvent>() where TEvent : EventBase
{
return (TEvent)eventDic[typeof(TEvent)];
}
/// <summary>
/// 添加事件类型
/// </summary>
/// <typeparam name="TEvent"></typeparam>
public void AddEvent<TEvent>() where TEvent : EventBase ,new()
{
lock (locker)
{
Type type = typeof(TEvent);
if (!eventDic.ContainsKey(type))
{
eventDic.Add(type, new TEvent());
}
}
}
/// <summary>
/// 移除事件类型
/// </summary>
/// <typeparam name="TEvent"></typeparam>
public void RemoveEvent<TEvent>() where TEvent : EventBase, new()
{
lock (locker)
{
Type type = typeof(TEvent);
if (eventDic.ContainsKey(type))
{
eventDic.Remove(type);
}
}
}
}
使用事件总线
事件及事件参数
使用事件总线前,需要定义好事件及事件参数。在使用时,发布者、订阅者也必须知道事件类型及事件参数类型。代码如下:
/// <summary>
/// 泛型事件实现-TestAEvent,重写事件的触发逻辑
/// </summary>
public class TestAEvent: PubSubEvent<TestAEventArgs>
{
public override void Publish(object sender, TestAEventArgs eventArgs)
{
lock (locker)
{
for (int i = 0; i < subscriptions.Count; i++)
{
var action= subscriptions[i];
Task.Run(() => action(sender, eventArgs));
}
}
}
}
/// <summary>
/// 泛型事件参数实现-TestAEventArgs
/// </summary>
public class TestAEventArgs : PubSubEventArgs<string> { }
/// <summary>
/// 泛型事件实现-TestBEvent
/// </summary>
public class TestBEvent : PubSubEvent<TestBEventArgs> { }
/// <summary>
/// 泛型事件参数实现-TestBEventArgs
/// </summary>
public class TestBEventArgs : PubSubEventArgs<int> { }
注:TestAEvent中重写了事件发布的逻辑,每个事件在任务中执行。
定义发布者
发布者通过事件总线获取事件实例,在实例上发布事件,代码如下:
class Publisher
{
public void PublishTeatAEvent(string value)
{
EventBus.Default.GetEvent<TestAEvent>().Publish(this, new TestAEventArgs() { Value=value});
}
public void PublishTeatBEvent(int value)
{
EventBus.Default.GetEvent<TestBEvent>().Publish(this, new TestBEventArgs() { Value = value });
}
}
定义订阅者
订阅者通过事件总线获取事件实例,在实例上订阅事件,代码如下:
class ScbscriberA
{
public string Name { get; set; }
public ScbscriberA(string name)
{
Name = name;
EventBus.Default.GetEvent<TestAEvent>().Subscribe(TeatAEventHandler);
}
public void TeatAEventHandler(object sender, TestAEventArgs e)
{
Console.WriteLine(Name+":"+e.Value);
}
}
class ScbscriberB
{
public string Name { get; set; }
public ScbscriberB(string name)
{
Name = name;
EventBus.Default.GetEvent<TestBEvent>().Subscribe(TeatBEventHandler);
}
public void Unsubscribe_TeatBEvent()
{
EventBus.Default.GetEvent<TestBEvent>().Unsubscribe(TeatBEventHandler);
}
public void TeatBEventHandler(object sender, TestBEventArgs e)
{
Console.WriteLine(Name + ":" + e.Value);
}
}
实际使用
代码如下:
class Program
{
static void Main(string[] args)
{
Publisher publisher = new Publisher();
ScbscriberA scbscriberA = new ScbscriberA("scbscriberA");
ScbscriberB scbscriberB1 = new ScbscriberB("scbscriberB1");
ScbscriberB scbscriberB2 = new ScbscriberB("scbscriberB2");
publisher.PublishTeatAEvent("test");
publisher.PublishTeatBEvent(123);
scbscriberB2.Unsubscribe_TeatBEvent();
publisher.PublishTeatBEvent(12345);
Console.ReadKey();
}
}
运行结果:
scbscriberB1:123
scbscriberB2:123
scbscriberA:test
scbscriberB1:12345
总结
这个事件总线只提供了基础功能,实现的发布者和订阅者的解耦,发布者、订阅者只依赖事件不互相依赖。
感觉我对事件总线的理解还有点不足,欢迎大家来一起讨论!
参考资料
c# – 使用反射来发现派生类型
Prism.Core/Events/EventBase.cs
C# 事件总线 EventBus
C#事件总线的更多相关文章
- Android事件总线
Android中Activity.Service.Fragment之间的相互通信比较麻烦,主要有以下一些方法: (1)使用广播,发送者发出广播,接收者接收广播后进行处理: (2)使用Handler和M ...
- ABP理论学习之事件总线和领域事件
返回总目录 本篇目录 事件总线 定义事件 触发事件 处理事件 句柄注册 取消注册 在C#中,我们可以在一个类中定义自己的事件,而其他的类可以注册该事件,当某些事情发生时,可以通知到该类.这对于桌面应用 ...
- Lind.DDD.Events事件总线~自动化注册
回到目录 让大叔兴奋的自动化注册 对于领域事件之前说过,在程序启动时订阅(注册)一些事件处理程序,然后在程序的具体位置去发布(触发)它,这是传统的pub/sub模式的体现,当然也没有什么问题,为了让它 ...
- Guava - EventBus(事件总线)
Guava在guava-libraries中为我们提供了事件总线EventBus库,它是事件发布订阅模式的实现,让我们能在领域驱动设计(DDD)中以事件的弱引用本质对我们的模块和领域边界很好的解耦设计 ...
- DDD~领域事件与事件总线
回到目录 谈谈它 终于有些眉目了,搜刮了很多牛人的资料,英文的,中文的,民国文的,终于小有成就了,同时也做了个DEMO,领域事件这东西好,但需要你明白它之后才会说好,而对于明白领域事件这件事来说,它的 ...
- Android学习系列(43)--使用事件总线框架EventBus和Otto
事件总线框架 针对事件提供统一订阅,发布以达到组件间通信的解决方案. 原理 观察者模式. EventBus和Otto 先看EventBus的官方定义: Android optimized event ...
- ASP.NET ZERO 学习 事件总线
用于注册和触发客户端的全局事件. 介绍 Pub/sub事件模型广泛用于客户端,ABP包含了一个简单的全局事件总线来 注册并 触发事件. 注册事件 可以使用abp.event.on来注册一个全局事件.一 ...
- 【DDD-Apwork框架】事件总线和事件聚合器
第一步:事件总线和事件聚合器 [1]事件总线 IEventBus IUnitOfWork.cs using System; using System.Collections.Generic; usin ...
- AndroidEventBus ( 事件总线 ) 的设计与实现
1. 功能介绍 AndroidEventBus是一个Android平台的事件总线库, 它简化了Activity.Fragment.Service等组件或者对象之间的交互,非常大程度上减少了它们之间的耦 ...
- Guava: 事件总线EventBus
EventBus 直译过来就是事件总线,它使用发布订阅模式支持组件之间的通信,不需要显式地注册回调,比观察者模式更灵活,可用于替换Java中传统的事件监听模式,EventBus的作用就是解耦,它不是通 ...
随机推荐
- 怎么用Markdown在github上写书,并用pages展示
怎么用git写书 安装环境 第一步 安装node npm 先检测自己电脑是否安装了node npm # 查看 node 版本 node -v # 查看 npm 版本 npm -v 复制代码 如果成功打 ...
- (2)MySQL进阶篇SQL优化(show status、explain分析)
1.概述 在应用系统开发过程中,由于初期数据量小,开发人员写SQL语句时更重视功能上的实现,但是当应用系统正式上线后,随着生产数据量的急剧增长,很多SQL语句开始逐渐显露出性能问题,对生产环境的影响也 ...
- C# AppDomain.CurrentDomain.BaseDirectory
AppDomain.CurrentDomain.BaseDirectory 是获取基目录,它由程序集冲突解决程序用来探测程序集.由显示的路径可以看出,它代表的是程序集所在的目录,它具有读取和写入的 ...
- 比Django官方实现更好的分页组件+Bootstrap整合
前言 Django全家桶自带的分页组件只能说能满足分页这个功能,但是没那么好用就是了 Django的分页效果 django-pure-pagination分页效果 使用方法 首先安装: pip ins ...
- Linux中Sshd服务配置文件优化版本(/etc/ssh/sshd_config)
Linux中Sshd服务配置文件优化版本(/etc/ssh/sshd_config) # $OpenBSD: sshd_config,v 1.93 2014/01/10 05:59:19 djm Ex ...
- [BFS]A. 【例题1】走迷宫
A . [ 例 题 1 ] 走 迷 宫 解析 简单的BFS模板题 Code #include <bits/stdc++.h> #define N 1005 using namespace ...
- 【spring cloud hoxton】Ribbon 真的能被 spring-cloud-loadbalancer 替代吗
背景 早上刷圈看到 Spring Cloud Hoxton.M2 Released 的消息,随手发布到了我的知识星球,过了会有个朋友过来如下问题. 抽取半天时间学习spring-cloud-loadb ...
- 虚拟机装好centos7没网解决办法
输入ip查询命名 ip addr 也可以输入 ifconfig(centOs7没有ifconfig命令)查看ip,但此命令会出现3个条目,centos的ip地址是ens33条目中的inet值. 发现 ...
- 全网最清楚的:MySQL的insert buffer和change buffer 串讲
目录 一.前言 二.问题引入 2.1.聚簇索引 2.2.普通索引 三.change buffer存在的意义 四.再看change buffer 五.change buffer 的限制 六.change ...
- 并发编程之ThreadLocal
并发编程之ThreadLocal 前言 当多线程访问共享可变数据时,涉及到线程间同步的问题,并不是所有时候,都要用到共享数据,所以就需要线程封闭出场了. 数据都被封闭在各自的线程之中,就不需要同步,这 ...