中介者类空引用最常见原因是未在DI容器注册或手动new时未传依赖;同事通信防循环调用需单向发送+守卫条件;中介者宜按生命周期选择强引用或WeakReference;MediatR适合标准通知场景但不适用复杂协调逻辑。

中介者类为什么总是空引用?
最常见原因是 Mediator 实例没被正确创建或注入,尤其在依赖注入场景下漏掉了注册。比如用 ASP.NET Core 时只写了 public class ChatRoomMediator : IMediator,却没在 Program.cs 里调用 services.AddScoped,结果运行时 _mediator 是 null。
另一个高频坑是手动 new 出中介者但没传入依赖对象——比如 new ChatRoomMediator() 直接调用,而构造函数里需要 IList 或 ILogger,这时要么参数全为 null,要么抛出 ArgumentNullException。
- 检查构造函数参数是否都有对应实例传入,或确认 DI 容器已注册全部依赖
- 在中介者方法入口加
if (_mediator == null) throw new InvalidOperationException("Mediator not initialized");快速暴露问题 - 避免在静态上下文(如静态字段、静态构造函数)中直接访问非静态中介者实例
同事对象(Colleague)如何安全通信而不循环调用?
中介者模式里最容易触发无限递归的地方:A 发消息给中介者 → 中介者通知 B → B 处理后又发消息给中介者 → 中介者再通知 A……最终栈溢出。典型错误是 User.Send() 内部直接调用 _mediator.Notify(this, message),而 Notify 又反过来调用所有 User.Receive(),其中某个 Receive 又触发了新的 Send。
关键约束是:同事对象只能向中介者「单向发送」,不能在 Receive 回调里主动再发新消息,除非明确加守卫条件。
- 在
Receive方法开头加标记位,如if (_isProcessing) return;,配合try/finally控制 - 把「响应逻辑」和「主动发起逻辑」拆开:响应只更新本地状态,真正要广播的动作由外部事件(如按钮点击)驱动
- 中介者内部对同一轮通知做去重,例如用
HashSet记录本轮已处理的发送方
中介者要不要持有同事对象的强引用?
要,但得看生命周期。如果同事对象(比如窗体控件、网络连接)比中介者活得短,强引用会导致内存泄漏——中介者一直 hold 着已关闭的窗体,GC 清不掉。
.NET 提供 WeakReference 是解法之一,但注意它只适用于「可容忍偶尔访问不到」的场景;若中介者必须保证每次都能找到目标同事(如实时聊天室),就得配合生命周期管理,比如在同事销毁前显式调用 _mediator.Unregister(user)。
- 短期共存(如同一个 ViewModel 内的多个子组件):直接用
List持有强引用,简单可靠 - 长期运行且同事动态增删(如插件系统):用
Dictionary+ 定期清理> - 别在
WeakReference.Target上直接链式调用,先判空:if (ref.TryGetTarget(out var colleague)) colleague.Receive(msg);
用 MediatR 库替代手写中介者值不值得?
值得,但不是无脑替换。MediatR 解决的是「请求-响应」和「通知」这两类标准通信,背后是基于泛型委托和反射的管道机制;它不解决「多对多状态同步」「带优先级的消息队列」或「跨进程中介」这类需求。
如果你的场景只是「用户点击按钮 → 更新数据 → 刷新列表 → 弹提示」,MediatR 的 INotification 足够干净;但若需要「A 发消息后等 B 和 C 都确认才继续」,手写中介者反而更容易控制流程。
- 引入 MediatR 后,所有
INotificationHandler必须被 DI 容器发现,漏注册会导致静默失败(没报错,但消息没人收) - 调试时注意:MediatR 默认不捕获中间件异常,
IPipelineBehavior抛异常会中断整个链路,建议加日志中间件 - 不要在
IRequestHandler里直接调用 UI 更新(如label.Text = "done"),那是线程上下文违规
真正难的从来不是“怎么写中介者”,而是判断哪部分逻辑该放进中介者、哪部分该留在同事内部——边界模糊时,先写测试,再看哪个类改一次要动七八个地方,那个就是该被中介化的点。










