详解c# 事件总线

目录
  • 简介
  • 实现事件总线
    • 定义事件基类
    • 定义事件参数基类
    • 定义EventBus
  • 使用事件总线
    • 事件及事件参数
    • 定义发布者
    • 定义订阅者
    • 实际使用
  • 总结

简介

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

实现事件总线

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# 事件总线的详细内容,更多关于c# 事件总线的资料请关注我们其它相关文章!

(0)

相关推荐

  • C#事件订阅发布实现原理详解

    我们用一个简单的例子,来说明一下这种消息传递的机制. 有一家三口,妈妈负责做饭,爸爸和孩子负责吃...将这三个人,想象成三个类. 妈妈有一个方法,叫做"做饭".有一个事件,叫做"开饭".做完饭后,调用开发事件,发布开饭消息. 爸爸和孩子分别有一个方法,叫做"吃饭". 将爸爸和孩子的"吃饭"方法,注册到妈妈的"开饭"事件.也就是,订阅妈妈的开饭消息.让妈妈做完饭开饭时,发布吃饭消息时,告诉爸爸和孩子一声. 这

  • 一篇文章彻底搞清楚c#中的委托与事件

    一.什么是委托呢? 听着名字挺抽象,确实不好理解.面试官最喜欢考察这个,而且更喜欢问:"委托和事件有何异同?".如果对一些知识点没有想明白,那么很容易被绕进去.研究任何事物,我们不妨从它的定义开始,委托也不例外.那么先来看c#中的委托定义,先来个例子: public delegate void GetPacage(string code); 这个委托,看起来就是个方法签名,取包裹,需要验证码.与方法签名不同的地方,在于多了一个delegate.c#中不乏一些便利好用的语法,比如fore

  • C#创建自定义控件及添加自定义属性和事件使用实例详解

    前言 C#本身提供了很强大的控件库,但是很多控件库的功能只是一些基本的功能,就比如最简单的按钮,C#提供了最基础的按钮使用方法,但是如果要增加一些功能,比如按钮按下要一个图片,弹起要另一个图片这样的工作.当然,我们可以对相关按钮在点击函数中进行更改其背景图片,但是这对于大量的按钮调用来说十分不方便,代码重用率相当的高,会导致使用上的卡顿,在这种情况下,我们可以将这个功能封装起来,这样每次调用起来就很方便,也节省了代码的重用率. 那么如何将上述的那么一个简单的功能封装起来呢,我们首先考虑到的便是按

  • 简单聊聊c# 事件

    引言: 前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听到"事件"这个概念的,尤其是写UI的时候,当我们点击一个按钮后VS就会自动帮我们生成一些后台的代码,然后我们就只需要在Click方法里面写代码就可以,所以可能有些刚接触C#的朋友就觉得这样很理所当然的,也没有去思考这是为什么的,为什么点击下事件就会触发我们在Click方法里面写的代码呢?事件到底扮演个什么样的角色呢?为了解除大家的这些疑惑,下面就详细介绍了事件,让一些初学者深入理解C#中的事件的概念. 一.为

  • C# 标准事件流实例代码

    服装价格变动,触发淘宝发布活动和消费者购买衣服事件流 public class EventStandard { public class Clothes { /// <summary> /// 服装编码 /// </summary> public string Id { get; set; } /// <summary> /// 服装名称 /// </summary> public string Name { get; set; } /// <summ

  • c#多线程通信之委托事件

    在研究c# 线程之间通信时,发现传统的方法大概有三种: 全局变量,由于同一进程下的多个进程之间共享数据空间,所以使用全局变量是最简单的方法,但要记住使用volatile进行限制. 线程之间发送消息(这个随后文章中会讨论到). CEvent为MFC中的一个对象,可以通过对CEvent的触发状态进行改变,从而实现线程间的通信和同步,这个主要是实现线程直接同步的一种方法. 本文介绍的一种方法是这三种之外的一种方法,本文中实例是通过创建一个线程类,通过委托事件把值传送到Form所在的类中,同时更新For

  • C#委托与事件原理及实例解析

    委托:个人在这里理解 委托就是 定义一个引用,一个可以记录函数指针的引用. public delegate void GreetingDelegate(int param); 事件:就是基于委托定义的. public event GreetingDelegate payxx; 其实这里的事件payxx 就差不多和string 一样可,只不过是存函数指针的变量. 这里上一个例子代码: using UnityEngine; using System.Collections; using UnityE

  • 详解C#之事件

    事件:定义了事件成员的类允许通知其他其他对象发生了特定的事情.具体的说,定义了事件成员的类能提供以下功能 1.方法能登记它对事件的关注 2.方法能注销它对事件的关注 3.事件发生时,登记了的方法将收到通知 类型之所以能提供事件通知功能,是因为类型维护了一个已登记方法的列表.事件发生后,类型将通知列表中所有已登记的方法. 事件是以委托为基础.委托是调用回调方法的一种类型安全的方式.对象凭借回调方法接收他们订阅的通知. 假如有一下场景:要设计一个电子邮件程序.当有新的邮件的到达时,用户希望做些一别的

  • c# 实现控件(ocx)中的事件详解

    c#控件实现类似c++中ocx控件功能 c++中ocx控件 1.控件方法 2.控件事件 c#很容易实现c++中ocx中控件方法的功能,但是实现类似c++中ocx的控件事件,则需要一定的周折. 下面就用实例简单的介绍c#如何实现 c#中ActiveX(ocx)实现实例(vs2008环境下): using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using

  • c#如何实现接口事件

    接口可以声明事件. 下面的示例演示如何在类中实现接口事件. 这些规则基本上都与实现任何接口方法或属性时的相同. 在类中实现接口事件 在类中声明事件,然后在相应区域中调用它. namespace ImplementInterfaceEvents { public interface IDrawingObject { event EventHandler ShapeChanged; } public class MyEventArgs : EventArgs { // class members }

随机推荐