MediatR源码解读(一):程序集扫描与Handler注册背后的泛型协变魔法
MediatR源码解读一程序集扫描与Handler注册背后的泛型协变魔法【免费下载链接】MediatRSimple, unambitious mediator implementation in .NET项目地址: https://gitcode.com/gh_mirrors/me/MediatRMediatR 是 .NET 生态中最流行的中介者Mediator模式库用简单、不冒进的设计把请求处理器Handler与调用方解耦。本文深入 MediatR 源码解读它启动时如何扫描程序集、把成百上千个 Handler 自动注册进依赖注入容器以及泛型逆变Covariance/Contravariance如何让一个 Handler 处理一族请求成为可能。无需逐行读懂反射也能理解这套注册魔法的完整链路。一、一行 AddMediatR 背后发生了什么大多数人在项目中只写过一行代码services.AddMediatR(cfg cfg.RegisterServicesFromAssemblyContainingPing());就是这一行MediatR 就完成了三件事入口在 MediatRServiceCollectionExtensions.cs校验至少要提供一个待扫描的程序集否则直接抛出参数异常扫描注册调用ServiceRegistrar.AddMediatRClassesWithTimeout扫描所有 Handler 并注册补全基础设施调用ServiceRegistrar.AddRequiredServices注册IMediator、ISender、IPublisher以及异常处理、前后置处理器等管道行为。 记住这条链路AddMediatR → 扫描程序集 → 找到 Handler → 注册为瞬态服务。后面三节分别拆解。二、程序集扫描三部曲容忍加载失败 递归查接口第 1 步安全地取出所有类型扫描的核心是拿到程序集中的全部类型。MediatR 没有直接调用assembly.GetTypes()而是封装了一个更健壮的方法见 ServiceRegistrar.csprivate static IEnumerableType GetLoadableDefinedTypes(this Assembly assembly) { try { return assembly.DefinedTypes; } catch (ReflectionTypeLoadException ex) { return ex.Types.OfTypeType(); } }为什么这么写大型程序集中只要有一个类型依赖缺失例如某个平台包没引用GetTypes()就会整体失败。这里捕获ReflectionTypeLoadException后只保留能加载的类型让扫描优雅降级而不是启动崩溃——这是开源库处理反射时的一个典范细节。第 2 步递归搜索闭包接口拿到类型后MediatR 用FindInterfacesThatCloseServiceRegistrar.cs判断一个类是否实现了IRequestHandler,等开放泛型模板。它的逻辑是一个沿基类链向上递归的算法检查当前类型实现的所有泛型接口其泛型定义是否等于模板如IRequestHandler,匹配到就收下来然后继续检查基类直到object为止。这意味着 Handler 可以通过继承中间基类间接实现接口依然能被扫描到。配合IsConcrete()排除抽象类和接口扫描只保留能 new 出来的实现类。第 3 步分类落袋——封闭类型 vs 开放泛型ConnectImplementationsToTypesClosingServiceRegistrar.cs把扫描结果分成两桶桶内容注册方式concretions普通封闭类如PingHandler : IRequestHandlerPing, Pong按接口精确注册TryAddTransient防重复genericConcretions开放泛型类如AuditHandlerT : IRequestHandlerT, ... where T : IAuditable延迟处理见第四节对于一个请求多个 Handler的场景通知、异常处理器参数addIfAlreadyExists true会让它们全部注册用AddTransient而非TryAddTransient最终由容器以IEnumerable的形式批量解析——这也是为什么 INotificationHandler.cs 的设计天然是一对多的。三、变体魔法为什么一个 Handler 能处理一族请求这是 MediatR 最精妙、也最容易被忽略的设计藏在接口定义的一个关键字里。看 IRequestHandler.cspublic interface IRequestHandlerin TRequest, TResponse where TRequest : IRequestTResponse注意那个in—— 它声明TRequest是**逆变contravariant**位置标题中的协变是泛型变体机制的统称。逆变带来的直接好处是IRequestHandlerOrder, string // 处理父类请求 // 在运行时可以赋值给 IRequestHandlerVIPOrder, string // 处理子类请求也就是说只要写了一个处理Order的 Handler当 MediatR 用反射查找IRequestHandlerVIPOrder, string时同一个 Handler 实例就能被容器解析出来。这个能力在扫描时由CanBeCastTo内部就是IsAssignableFrom落地var exactMatches concretions.Where(x x.CanBeCastTo(interface)).ToList();IsAssignableFrom比较类型时会自动套用泛型变体规则所以子类请求 → 父类 Handler的匹配是免费获得的MediatR 一行额外代码都没写。 实战意义做审计、权限校验这类横切逻辑时给请求定义一个公共接口如IAuditable写一个针对基接口/父类型的 Handler所有派生请求全部被覆盖。样本代码 GenericHandler.cs 正是这个思路一个INotificationHandlerINotification接住全部通知。还有一个容易踩的坑在 IRequestHandler.cs 的第二个参数TResponse没有in修饰符是**不变invariant**的。变体是双向约束——TRequest逆变保证父 Handler 能接子类请求TResponse不变则保证响应类型必须精确一致防止返回类型被意外替换。这个细节决定了注册时响应类型必须与请求声明的IRequestTResponse完全对上。四、开放泛型 Handler组合爆炸与四道安全闸最魔幻的场景是用泛型写通用 Handlerpublic class AuditHandlerT : IRequestHandlerT, Unit where T : IAuditable { ... }问题来了IAuditable可能有 10 个实现那么要注册 10 个具体化版本100 个呢MediatR 的答案是——在启动时穷举所有合法组合逐一具体化注册。流程在AddAllConcretionsThatClose→GetConcreteRequestTypesServiceRegistrar.cs读取开放泛型 Handler 每个泛型参数的约束如where T : IAuditable在程序集中筛出所有满足约束的具体类得到每个参数各自的候选列表用递归GenerateCombinations做笛卡尔积生成全部组合对每个组合调用MakeGenericType具体化逐条AddTransient注册。比如请求是ReportRequestTData, TFormatTData有 3 种、TFormat有 2 种就会自动注册 3×26 个 Handler。四道安全闸防止注册风暴组合数是乘法增长的一个手滑就能让应用启动卡死。MediatR 在 MediatrServiceConfiguration.cs 中提供了四道可配置的保险丝配置项默认值作用MaxGenericTypeParameters10限制单个泛型请求的参数个数MaxTypesClosing100限制单个参数可闭合的类型数MaxGenericTypeRegistrations125000限制总注册组合数RegistrationTimeout15000ms整个注册过程的总超时超时抛TimeoutException任何一道闸触发都会抛出带明确信息的ArgumentException/TimeoutException告诉你是哪个类型、哪个参数越界。配套的单元测试甚至用AssemblyBuilder动态生成越界的程序集来验证这些防线见 BaseGenericRequestHandlerTests.cs 和 GenericRequestHandlerTests.cs覆盖全部组合都注册、数量正确、无重复三类断言。⚠️ 新手提醒开放泛型 Handler 默认不启用RegisterGenericHandlers默认为false需要在配置中显式打开这正是防误伤的又一重保险。五、收尾AddRequiredServices 与运行时解析扫描完成后AddRequiredServicesServiceRegistrar.cs负责基础设施装配其中两处值得留意管道顺序先注册前置处理器PreProcessor再后置处理器PostProcessor最后才是普通 Behavior保证请求按前置 → 处理 → 后置顺序流经管道异常处理器的策略开关RequestExceptionActionProcessorStrategy决定异常捕获器排在异常处理器之前还是之后默认只对未被捕获的异常生效避免与常规 try/catch 语义冲突。注册是建索引运行才是查字典。看 Mediator.cs 的Send它并不每次反射而是用ConcurrentDictionaryType, ...按请求类型缓存一个 Handler 委托包装器首次解析时生成并缓存。所以注册阶段的繁重扫描只付一次成本热路径上Send几乎是一次字典查找 一次容器解析——这是 MediatR 性能友好的关键。六、源码导读路线图把本文内容对应到仓库文件建议按此顺序阅读顺序文件看点1MediatRServiceCollectionExtensions.csAddMediatR入口与整体流程2MediatrServiceConfiguration.cs所有配置项与默认值3ServiceRegistrar.cs扫描、变体匹配、组合生成的全部魔法4IRequestHandler.cs / INotificationHandler.cs接口上的in变体声明5Mediator.cs运行时解析与委托缓存6samples/MediatR.Examples/Ping/Pong 等可运行示例小结MediatR 的程序集扫描 Handler 注册看似一行AddMediatR的魔法实际是四层设计叠加的结果容错反射保证扫描不崩、接口模板递归匹配保证找得全、泛型逆变保证一个 Handler 覆盖一族请求、四道限流闸保证组合爆炸可控。理解了这条注册链路你再看 MediatR 的管道行为Pipeline Behavior与自定义IPipelineBehavior就会顺理成章。下一篇将深入管道机制请求是如何在 PreProcessor、Handler、PostProcessor 之间流动并被异常处理器拦截的。【免费下载链接】MediatRSimple, unambitious mediator implementation in .NET项目地址: https://gitcode.com/gh_mirrors/me/MediatR创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

matplotlib动画实战:FuncAnimation绘制小人发射爱心

matplotlib动画实战:FuncAnimation绘制小人发射爱心

简介:使用Python的turtle模块绘制“小人发射爱心”图形,是这份PDF教程的核心内容。资源面向Python初学者与趣味编程爱好者,通过一个完整可运行的示例,演示了如何利用标准库turtle实现图形绘制:从定义go_to、head、leg、…

2026/9/19 21:01:25 阅读更多 →
用 Espresso 驱动 Flutter Android 应用:espresso 包原生 UI 集成测试实战指南

用 Espresso 驱动 Flutter Android 应用:espresso 包原生 UI 集成测试实战指南

用 Espresso 驱动 Flutter Android 应用:espresso 包原生 UI 集成测试实战指南 【免费下载链接】packages A collection of useful packages maintained by the Flutter team 项目地址: https://gitcode.com/GitHub_Trending/pac/packages 本篇技术指南围绕 …

2026/9/19 21:00:25 阅读更多 →
2013 年旧 Mac 跑 macOS Sequoia 可行吗?OpenCore Legacy Patcher 完整指南

2013 年旧 Mac 跑 macOS Sequoia 可行吗?OpenCore Legacy Patcher 完整指南

2013 年旧 Mac 跑 macOS Sequoia 可行吗?OpenCore Legacy Patcher 完整指南 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher OpenCore Legacy Patc…

2026/9/19 21:00:25 阅读更多 →

最新新闻

Unity虚拟展馆开发实战:汉绣非遗三维交互与性能优化

Unity虚拟展馆开发实战:汉绣非遗三维交互与性能优化

1. 项目缘起与整体设计思路汉绣作为国家级非物质文化遗产,其针法体系、纹样谱系和色彩规范都有一套完整的视觉语言。但现实情况是,真正能接触到汉绣实物的人并不多——展馆分布有限,绣品又极其脆弱,光照、温湿度、触摸都会造成不可…

2026/9/19 22:36:11 阅读更多 →
轻量级大语言模型OpenClaw本地部署指南

轻量级大语言模型OpenClaw本地部署指南

1. 项目概述作为一名长期关注AI技术落地的从业者,我注意到很多初学者在接触大模型时面临两大难题:一是商业API调用成本高,二是本地部署门槛高。今天要分享的OpenClaw(小龙虾)项目恰好解决了这两个痛点 - 这是一个可以在…

2026/9/19 22:36:11 阅读更多 →
C语言课程设计实战:从销售管理源码看结构体与文件读写那些坑

C语言课程设计实战:从销售管理源码看结构体与文件读写那些坑

简介:这套C语言课程设计实验文档以笔记本电脑销售管理系统为主题,适合高校计算机专业学生在课程设计、实验报告撰写或期末项目准备时参考。文档系统梳理了创建产品销售信息文件、增加信息、删除信息、修改信息、销售管理、查询操作以及统计某品牌销量最高…

2026/9/19 22:36:11 阅读更多 →
Windows权限报错解析:Administrators权限问题排查与解决

Windows权限报错解析:Administrators权限问题排查与解决

1. 这个报错到底在说什么“你需要来自 Administrators 的权限才能对此文件夹进行更改”——这句话几乎每个 Windows 用户都见过,但真正理解它含义的人并不多。大多数人看到它的第一反应是“我明明是这台电脑的管理员啊,为什么还说我没权限”,…

2026/9/19 22:36:11 阅读更多 →
使用 Epley 公式估算 1RM:One-Rep Max Calculator 命令行版与 Tkinter GUI 版源码解析

使用 Epley 公式估算 1RM:One-Rep Max Calculator 命令行版与 Tkinter GUI 版源码解析

使用 Epley 公式估算 1RM:One-Rep Max Calculator 命令行版与 Tkinter GUI 版源码解析 【免费下载链接】Python My Python Examples 项目地址: https://gitcode.com/gh_mirrors/py/Python 导读 本文围绕仓库 nitkarshchourasia/to_sort/one_rep_max_calcula…

2026/9/19 22:36:11 阅读更多 →
一个能看懂显微镜照片、听懂鸟鸣声、读懂地震波的AI科学家

一个能看懂显微镜照片、听懂鸟鸣声、读懂地震波的AI科学家

你有没有想过,一个AI要做科研,到底缺的是什么?大部分人的第一反应是:算力不够,或者模型不够聪明。但2026年这篇论文给出的答案完全不一样。它说,现在那些能自动写论文、自动跑实验的AI科学家系统&#xff0…

2026/9/19 22:35:11 阅读更多 →

日新闻

BP神经网络时序预测:滑窗长度与多窗口平均策略

BP神经网络时序预测:滑窗长度与多窗口平均策略

简介:面向机器学习、深度学习与数据建模学习者的一份完整研究文献,聚焦BP神经网络在农业产量预测中的应用。文档以1980—2018年全国棉花产量为样本,系统讲解数据归一化处理、激活函数原理、多层神经网络结构搭建及训练流程,展示敏…

2026/9/19 0:00:30 阅读更多 →
Transformer训练实时监控实战:基于MindSpore的损失曲线可视化方案

Transformer训练实时监控实战:基于MindSpore的损失曲线可视化方案

上个月调一个Deformable DETR模型,在单卡上要跑将近两天。第二天早上我下意识打开终端翻日志,发现loss从凌晨两点就开始往上爬,一路从0.8涨到1.35,整整六个小时没人发现。那六个小时的训练不仅白跑,还霸占着卡——等于…

2026/9/19 0:00:30 阅读更多 →
OpenCloud 中的 Go 类型安全转换库 spf13/cast:从零值回退到泛型 API 的完整实战指南

OpenCloud 中的 Go 类型安全转换库 spf13/cast:从零值回退到泛型 API 的完整实战指南

OpenCloud 中的 Go 类型安全转换库 spf13/cast:从零值回退到泛型 API 的完整实战指南 【免费下载链接】opencloud 🌤️ OpenCloud is the open source platform for file management, sharing and collaboration. Simple and sovereign. 项目地址: htt…

2026/9/19 0:00:30 阅读更多 →

周新闻

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验 【免费下载链接】ai The AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and ag…

2026/9/19 3:59:36 阅读更多 →
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化

Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化

Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化 【免费下载链接】refine A React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility. 项目地址: https://gitcode.com/GitH…

2026/9/19 3:53:08 阅读更多 →
Flutter应用改名全指南:从Android到iOS的配置与工具实践

Flutter应用改名全指南:从Android到iOS的配置与工具实践

刚接一个外包项目时,甲方要求把工程里临时用的应用名改成正式产品名。我本来觉得“改名”这种小事,打开配置文件改一行不就完了?结果真动手才发现,Flutter项目里“应用名称”根本不是一处配置,而是一整套散落在 Androi…

2026/9/19 4:02:43 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/16 22:31:27 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/19 17:50:38 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/16 22:32:59 阅读更多 →