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的作用就是解耦,它不是通 ...
 
随机推荐
- 一键获取linux内存、cpu、磁盘IO等信息脚本编写,及其原理详解
			
更多linux知识,请关注公众号:一口Linux 一.脚本 今天主要分享一个shell脚本,用来获取linux系统CPU.内存.磁盘IO等信息. #!/bin/bash # 获取要监控的本地服务器IP ...
 - pip软件包管理工具介绍及基本使用
			
pip软件包管理工具介绍及基本使用 一分耕耘,一分收获,要收获得好,必须耕耘得好.-- 徐特立 一.pip软件包管理工具介绍: 定义:pip是Python包管理工具 作用:对Python包的查找.下载 ...
 - Linux+mysql混杂
			
一.linux 1.linux中给某一文件中批量新增一个内容 先vim进入文件,然后先按ctrl+v 然后选中需要的行数, 在shift+i 写你要添加的东西 然后在按下esc 二,mysql 1.l ...
 - PTA 求二叉树的深度
			
6-7 求二叉树的深度 (6 分) 本题要求实现一个函数,可返回二叉树的深度. 函数接口定义: int Depth(BiTree T); T是二叉树树根指针,函数Depth返回二叉树的深度,若树为 ...
 - x64 下记事本WriteFile() API钩取
			
<逆向工程核心原理>第30章 记事本WriteFile() API钩取 原文是在x86下,而在x64下函数调用方式为fastcall,前4个参数保存在寄存器中.在原代码基础上进行修改: 1 ...
 - 7、Spring教程之使用注解开发
			
1.说明 在spring4之后,想要使用注解形式,必须得要引入aop的包 <dependency> <groupId>org.springframework</group ...
 - 历史性突破:使用 .net core 日处理消息量超过 1.7 万条!
			
业余时间用 .net core 写了一个在线客服系统.并在博客园写了一个系列的文章,写介绍这个开发过程: .net core 和 WPF 开发升讯威在线客服系统:目录 https://blog.she ...
 - 体验用yarp当网关
			
Yarp是微软开源的一个用.net实现的反向代理工具包,github库就叫reverse-proxy(反向代理)(吐槽一下微软起名字233333) nuget包preview9之前都叫Microsof ...
 - Mokito 单元测试与 Spring-Boot 集成测试
			
Mokito 单元测试与 Spring-Boot 集成测试 版本说明 Java:1.8 JUnit:5.x Mokito:3.x H2:1.4.200 spring-boot-starter-test ...
 - 多图详解 TCP 连接管理,太全了!!!
			
TCP 是一种面向连接的单播协议,在 TCP 中,并不存在多播.广播的这种行为,因为 TCP 报文段中能明确发送方和接受方的 IP 地址. 在发送数据前,相互通信的双方(即发送方和接受方)需要建立一条 ...