mef 的 compositioncontainer 负责执行组合,composablepartcatalog 负责声明可导入/导出的类型;必须先配置 catalog,否则 composeparts 会静默失败或抛 changerejectedexception。

什么是 MEF 的 CompositionContainer 和 ComposablePartCatalog
MEF 的核心是把类型发现、依赖解析、实例创建这三件事拆开:用 ComposablePartCatalog 告诉框架“哪些类型可被导入/导出”,再用 CompositionContainer 执行实际的组合。不先配置好 catalog,CompositionContainer 就像没装弹药的枪——调用 ComposeParts 会静默失败或抛 ChangeRejectedException。
常用 catalog 类型有:AssemblyCatalog(从指定程序集扫描)、DirectoryCatalog(扫描目录下所有 DLL)、AggregateCatalog(合并多个 catalog)。注意 DirectoryCatalog 不会递归子目录,且只加载能成功 Assembly.LoadFrom 的文件;若插件 DLL 依赖未部署的第三方库,它会被跳过,也不会报错——这是最常被忽略的“静默丢失插件”原因。
- 调试时可遍历
catalog.Parts确认导出类型是否被识别 - 避免在
DirectoryCatalog路径中混入 PDB 或 XML 文档文件,它们可能触发BadImageFormatException - 使用
AggregateCatalog时,后添加的 catalog 优先级更高(同名 contract 会被覆盖)
[Import] 和 [Export] 必须成对出现且 contract 匹配
MEF 不靠类型名匹配,而是靠 contract name。默认 contract name 是导出类型的全名(如 "MyPlugin.ICommand"),但一旦你给 [Export] 指定了字符串参数,比如 [Export("IHandler")],那 [Import] 就必须写成 [Import("IHandler")],否则组合失败。更隐蔽的问题是:如果导出类实现了多个接口,又没显式指定 contract,MEF 会为每个接口生成一个导出,但 [Import] 若只写接口类型,可能意外绑定到非预期的那个。
建议统一用命名 contract + 接口类型约束:
[Export(typeof(ICommand), "FileSaveCommand")]
public class SaveCommand : ICommand { ... }
[Import("FileSaveCommand")]
public ICommand SaveCommand { get; set; }
-
[Import]属性字段不能是private,必须是public或internal(且类本身需标记[PartCreationPolicy(CreationPolicy.NonShared)]才支持 private set) - 不要在构造函数里直接
new导入对象——MEF 还没开始组合,此时字段为null - 若需要延迟加载,用
[ImportLazy]配合Lazy<t></t>,避免提前实例化代价高的插件
如何安全释放插件并处理 CompositionException
MEF 默认不管理插件生命周期,CompositionContainer 本身也不实现 IDisposable,但如果你用了 DirectoryCatalog 并希望热替换插件,就必须手动清理:先调用 container.ReleaseExport(仅对 ExportLifetimeContext<t></t> 有效),再重建 container 和 catalog。更现实的做法是——别热更新,重启进程。
组合失败时抛出的 CompositionException 错误信息非常简陋,只说“无法满足导入”,真正原因藏在 Exception.Errors 里。必须遍历这个集合才能看到具体哪条导入失败、缺哪个导出、冲突在哪。
- 永远用
try/catch (CompositionException ex)包裹container.ComposeParts(this) - 检查
ex.Errors.Select(e => e.Element.DisplayName)定位到具体属性或构造函数参数 - 若插件抛异常导致构造失败,MEF 会包装成
CompositionContractMismatchException,根源还是插件自身逻辑问题
.NET Core/.NET 5+ 中为什么 System.ComponentModel.Composition 不推荐用了
原生 MEF(即 System.ComponentModel.Composition)在 .NET Core 一开始就未被移植,官方只提供了兼容层(Microsoft.Composition),但它不支持 DirectoryCatalog、没有异步组合、且已停止维护。现在标准做法是用 Microsoft.Extensions.DependencyInjection 配合手动扫描程序集,或改用 MEF2(System.Composition 命名空间),后者是轻量重写版,支持 .NET Standard 2.0+,但语法略有差异:比如 [Export] 不再接受字符串 contract name,必须用泛型 [Export<icommand>]</icommand>,且 CompositionHost 替代了 CompositionContainer。
如果你正在新项目中考虑插件化,直接跳过传统 MEF——用 AssemblyLoadContext 加反射扫描 + DI 容器注册,控制力更强,调试路径更直。
MEF 的抽象层级其实很高,一旦组合行为不符合预期,排查链路远比手动 new 对象加字典映射来得晦涩。特别是跨版本、混合使用不同 MEF 实现时,contract 解析规则的微小差异就足以让插件彻底失联。










