C#完成一个应用内的消息中心

本文会讲解如何使用 C# 完成一个应用内部的消息中心(事件总线),事件驱动最大的好处就是可以很大程度的解耦合,松散结构。

模式介绍

简单来说就是一个发布订阅模式设计,对于消息的产生者来说,并不对直接将消息发送给消息的处理者,而是将消息发送给独立的消息中心,这里和观察者模式做个区别。对于消息产生者来说,无需知道有哪些订阅者。而对于订阅者来说,同样无需知道消息发布者的存在。

实现

使用 C# 实现如下,一个消息中心有两个方法,一个是发布消息:Send/Publish,有一个是订阅消息:Subscribe

public static class MessageCenter
{
    public static void Send(object sender, string name, object data)
    {
        InnerSend(sender, name, data);
    }
    
     public static void Subscribe<TSender, TData>(object subscriber, string name, Action<TSender, TData> action)
    {
        InnerSubscribe(subscriber, name, action?.Method, action?.Target);
    }
}

接着实现 InnerSend ,发布消息本质就是查询所有的订阅者,并执行订阅的响应事件

private static void InnerSend(object sender, string name, object data)
{
    if (string.IsNullOrEmpty(name))
        throw new NullReferenceException();
    if (subs.TryGetValue(name, out var subscriptions))
    {
        foreach (var subscription in subscriptions)
        {
            subscription.Invoke(sender, data);
        }
    }
}

然后是 InnerSubscribe,将订阅者添加到订阅集合里

private static void InnerSubscribe(object subscriber, string name, MethodInfo methodInfo, object target)
{
    if (subscriber == null)
        throw new NullReferenceException();
    if (string.IsNullOrEmpty(name))
        throw new NullReferenceException();
    var sub = new Subscription(subscriber, methodInfo, target);
    if (subs.ContainsKey(name))
    {
        subs[name].Add(sub);
        return;
    }
    subs.Add(name, new List<Subscription>() { sub });
}

接下来实现订阅类,他将订阅者规范成统一的类型,方便消息传递

class Subscription
{
    public Subscription(object source, MethodInfo methodInfo, object target)
    {
        Source = source;
        MethodInfo = methodInfo;
        Target = target;
    }
    public object Source { get; set; }
    public MethodInfo MethodInfo { get; set; }
    public object Target { get; set; }
    
    //执行订阅响应
    public void Invoke(object sender, object data)
    {
        if (sender == null || sender == Source) return;
        if (MethodInfo.IsStatic)
        {
            MethodInfo.Invoke(null, MethodInfo.GetParameters().Length == 1 ? new[] { sender } : new[] { sender, data });
            return;
        }
        MethodInfo?.Invoke(Target, MethodInfo.GetParameters().Length == 2 ? new[] { sender, data } : new[] { sender });
    }
}

最后,别忘了使用字典把订阅者存下来

public static class MessageCenter
{
    private static Dictionary<string, List<Subscription>> subs = new Dictionary<string, List<Subscription>>();
    
    //.....内部代码
    
}

使用也是非常的方便,Bar 向消息中心订阅通道,Foo 向消息中心推送消息,完成后会打印出从 Foo 传到 Bar 的消息

public class Bar
{
    public void Start()
    {
        MessageCenter.SubScribe<Foo,string>(this, "测试通道", Handle);
    }
    
    private void Handle(Foo sender,string data)
    {
        Console.WriteLine(data);
    }
}

public class Foo
{
    public void Start()
    {
        MessageCenter.Send(this, "测试通道", "这是一条测试信息");
    }
}
Foo foo = new Foo();
Bar bar = new Bar();
//注意,这里要先注册订阅
bar.Start();
foo.Start();
发布时间:2021-11-26
其他阅读

命令行打包.net项目

.net 日常开发中,我们接触最多的就是 Visual Studio ,它是微软为了 .net 平台专门打造的 IDE (集成开发环境),为整个 .net 平台开发带来了无与伦比的图形化体验,但是有时候,我们也会遇到需要通过命令行来生成 .net 项目的情况,本文会介绍几种命令行打包的姿势。

查看原文

记录Unity中的坑

Unity虽然使用C#来进行开发,但是Unity所使用的运行时和.net的原生运行时却又差别,这导致在.net中的某些代码块在Unity中运行会出现错误。

查看原文

什么是上帝对象

上帝对象(God Object)是面向对象编程(OOP)中一种特殊现象,在面向对象编程中,我们把逻辑和数据封装成一个个对象,每个对象各司其职,最终实现我们的业务,但是如果业务复杂的情况下,可能会出现一个对象封装太多的数据或者担任了太多的职责的情况,这样的对象我们称为上帝对象。

查看原文

Entity Framework Core链接Oracle的问题

最近使用 Entity Framework Core 来链接 Oracle 11g 数据库,发现很多 LINQ 语句使用起来都会有问题,查看日志输出发现是生成SQL语句无法被 11g 版本执行,好在Oracle官方已经给我们提供了解决方案。

查看原文

我的读书清单

分享互联网人的读书清单,从2024年开始,本文会整理我读过的书籍,按时间排序,编辑成一份读书清单。

查看原文