简介

事件总线是对发布-订阅模式的一种实现,是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。

实现事件总线

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#事件总线的更多相关文章

  1. Android事件总线

    Android中Activity.Service.Fragment之间的相互通信比较麻烦,主要有以下一些方法: (1)使用广播,发送者发出广播,接收者接收广播后进行处理: (2)使用Handler和M ...

  2. ABP理论学习之事件总线和领域事件

    返回总目录 本篇目录 事件总线 定义事件 触发事件 处理事件 句柄注册 取消注册 在C#中,我们可以在一个类中定义自己的事件,而其他的类可以注册该事件,当某些事情发生时,可以通知到该类.这对于桌面应用 ...

  3. Lind.DDD.Events事件总线~自动化注册

    回到目录 让大叔兴奋的自动化注册 对于领域事件之前说过,在程序启动时订阅(注册)一些事件处理程序,然后在程序的具体位置去发布(触发)它,这是传统的pub/sub模式的体现,当然也没有什么问题,为了让它 ...

  4. Guava - EventBus(事件总线)

    Guava在guava-libraries中为我们提供了事件总线EventBus库,它是事件发布订阅模式的实现,让我们能在领域驱动设计(DDD)中以事件的弱引用本质对我们的模块和领域边界很好的解耦设计 ...

  5. DDD~领域事件与事件总线

    回到目录 谈谈它 终于有些眉目了,搜刮了很多牛人的资料,英文的,中文的,民国文的,终于小有成就了,同时也做了个DEMO,领域事件这东西好,但需要你明白它之后才会说好,而对于明白领域事件这件事来说,它的 ...

  6. Android学习系列(43)--使用事件总线框架EventBus和Otto

    事件总线框架 针对事件提供统一订阅,发布以达到组件间通信的解决方案. 原理 观察者模式. EventBus和Otto 先看EventBus的官方定义: Android optimized event ...

  7. ASP.NET ZERO 学习 事件总线

    用于注册和触发客户端的全局事件. 介绍 Pub/sub事件模型广泛用于客户端,ABP包含了一个简单的全局事件总线来 注册并 触发事件. 注册事件 可以使用abp.event.on来注册一个全局事件.一 ...

  8. 【DDD-Apwork框架】事件总线和事件聚合器

    第一步:事件总线和事件聚合器 [1]事件总线 IEventBus IUnitOfWork.cs using System; using System.Collections.Generic; usin ...

  9. AndroidEventBus ( 事件总线 ) 的设计与实现

    1. 功能介绍 AndroidEventBus是一个Android平台的事件总线库, 它简化了Activity.Fragment.Service等组件或者对象之间的交互,非常大程度上减少了它们之间的耦 ...

  10. Guava: 事件总线EventBus

    EventBus 直译过来就是事件总线,它使用发布订阅模式支持组件之间的通信,不需要显式地注册回调,比观察者模式更灵活,可用于替换Java中传统的事件监听模式,EventBus的作用就是解耦,它不是通 ...

随机推荐

  1. Tornado 简明教程

    1.TornadoTornado:python编写的web服务器兼web应用框架1.1.Tornado的优势轻量级web框架异步非阻塞IO处理方式出色的抗负载能力优异的处理性能,不依赖多进程/多线程, ...

  2. CSS行内元素盒模型

    1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...

  3. UML类图画法整理

    一 类图画法 1.类图的概念 显示出类.接口以及他们的静态结构和关系,用于描述系统的结构化设计. 2.类 类是对一组具有相同属性.操作.关系和语义对象的抽象,是面向对象的核心,包括名称.属性和方法.如 ...

  4. ImportError: No module named site

    cmd中执行python提示:ImportError: No module named site 运行python.exe Fatal Python error: initfsencoding: un ...

  5. webgoat白盒审计+漏洞测试

    前言 小白,记录,有问题可以交流 乖乖放上参考链接: https://www.freebuf.com/column/221947.html https://www.sec-un.org/java代码审 ...

  6. frp穿透内网使用vsftpd服务

    本篇文章将会介绍如何使用frp穿透内网以及如何在centos8环境下安装和使用vsftpd,最后在公网通过frp穿透内网使用ftp. 一.内网穿透神器frp frp 是一个专注于内网穿透的高性能的反向 ...

  7. PAT (Advanced Level) Practice 1019 General Palindromic Number (20 分) 凌宸1642

    PAT (Advanced Level) Practice 1019 General Palindromic Number (20 分) 凌宸1642 题目描述: A number that will ...

  8. 极简实用的Asp.NetCore模块化框架新增CMS模块

    简介 关于这个框架的背景,在前面我已经交代过了.不清楚的可以查看这个链接 极简实用的Asp.NetCore模块化框架决定免费开源了 在最近一段时间内,对这个框架新增了以下功能: 1.新增了CMS模块, ...

  9. Apache Hudi核心概念一网打尽

    1. 场景 https://hudi.apache.org/docs/use_cases.html 近实时写入 减少碎片化工具的使用 CDC 增量导入 RDBMS 数据 限制小文件的大小和数量 近实时 ...

  10. (6)MySQL进阶篇SQL优化(MyISAM表锁)

    1.MySQL锁概述 锁是计算机协调多个进程或线程并发访问某一资源的机制.在数据库中,除传统的计算资源 (如 CPU.RAM.I/O 等)的抢占以外,数据也是一种供许多用户共享的资源.如何保证数 据并 ...