EventAggregator, EventBus的实现
目录
系列主题:基于消息的软件架构模型演变
系列主题:基于消息的软件架构模型演变
.net中事件模型很优雅的实现了观察者模式,同时被大量的使用在各种框架中。如果我们非要给事件模型挑毛病,我觉得有两点:
- 实现起来略微繁琐
- 正如我们上篇文章分析,事件模型在特定的情况下会发生内存泄漏
于是我们想到了更加简单易用的模型:EventAggregator,正如该名称所描述,EventAggregator将观察者都聚合在一个容器里,向EventAggregator发布一个主题,EventAggregator会找到对该主题感兴趣的观察者并通知他。
Prism框架中实现了一个典型的EventAggregator,有时候我们又把此类实现叫做EventBus。
MVVM Light中实现了一个叫Messenger的bus,Messenger是EventBus、EventAggregator等概念的抽象,因为此时的主题已经表现的像是消息一样,所以Messenger这一名称也更加靠近基于消息架构这一主题。
一、实现一个简单的EventBus
EventBus的职责有两点:
- 注册观察者
- 发布主题
所以接口定义为:
1 2 3 4 5 | public interface ISimpleEventBus { void Register<TEvent>(Action<TEvent> action); void Publish<TEvent>(TEvent @ event ); } |
所有的主题都可以用Action<TEvent> action来表示,实现起来也很简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public class SimpleEventBus { public static ConcurrentDictionary<Type,List<Action< object >>> Dictionary= new ConcurrentDictionary<Type, List<Action< object >>>(); public void Register<TEvent>(Action<TEvent> action) { List<Action< object >> actionList; if (!Dictionary.TryGetValue( typeof (TEvent), out actionList)) { actionList= new List<Action< object >>(); Dictionary[ typeof (TEvent)] = actionList; } actionList.Add(o=>action((TEvent)o)); } public void Publish<TEvent>(TEvent @ event ) { List<Action< object >> actionList; if (Dictionary.TryGetValue( typeof (TEvent), out actionList)) { foreach ( var action in actionList) { action(@ event ); } } } } |
EventBus内部通过一个类型为ConcurrentDictionary<Type,List<Action<object>>> 的字典来存储主题和观察者列表。写个测试试试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | [Test] public void Should_handle_registered_action() { var eventBus = new SimpleEventBus(); var number = 0; eventBus.Register<MessageA>(m=>number=m.Number); eventBus.Publish( new MessageA(2)); number.Should().Be(2); } internal class MessageA { public MessageA( int number) { Number = number; } public int Number { get ; private set ; } } |
我们自己写的这个simpleEventBus已经能够应付大部分情况了,使用起来也比事件模型简单很多。但是仍然没有解决内存泄漏的问题。
二、MVVM Light中Messenger的实现
Messenger的实现是我们这个简单eventBus的升级版,首先他抽象了概念,从事件到消息是思维的一个转变,Messenger认为所有注册的主题都是消息。其次采用WeakReference来关联观察者和主题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | public void Register<TMessage>( object recipient, Action<TMessage> action) { lock (_registerLock) { var messageType = typeof (TMessage); Dictionary<Type, List<WeakAction>> recipients; if (_recipientsStrictAction == null ) { _recipientsStrictAction = new Dictionary<Type, List<WeakAction>>(); } recipients = _recipientsStrictAction; lock (recipients) { List<WeakAction> list; if (!recipients.ContainsKey(messageType)) { list = new List<WeakAction>(); recipients.Add(messageType, list); } else { list = recipients[messageType]; } var weakAction = new WeakAction<TMessage>(recipient, action); list.Add(weakAction); } } RequestCleanup(); } |
这个实现跟我们实现的EventBus大同小异,不同之处是dictionary类型为Dictionary<Type, List<WeakAction>>。
WeakAction的构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public WeakAction( object target, Action<T> action) { if (action.Method.IsStatic) { _staticAction = action; if (target != null ) { Reference = new WeakReference(target); } return ; } Method = action.Method; ActionReference = new WeakReference(action.Target); Reference = new WeakReference(target); } |
ActionReference = new WeakReference(action.Target); 这句话将一个object包装进了WeakReference。
发送消息的代码也跟我们自己实现的EventBus大同小异,大家可以直接看代码对比。
写一个测试看看如何使用Messenger:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | [Test] public void Should_handle_registered_actions() { int number = 0; Messenger.Default.Register<MessageA>( this ,m=>number=m.Number); Messenger.Default.Send( new MessageA(2)); number.Should().Be(2); } internal class MessageA { public MessageA( int number) { Number = number; } public int Number { get ; private set ; } } |
我们注意到Messenger采用了消息的概念,所以发布主题也将方法名从publish改为了send。一般我们都说发布一个事件,发送一个消息。Messenger所提到的概念已经快要接近ServiceBus了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?