MEF子容器覆盖导出失效:如何让MyCalculator获取子容器实例?
这个问题我之前在大型WPF+MEF项目里也踩过坑!核心原因是MEF的部件上下文绑定特性:当MyCalculator这种部件是在根容器的目录里被发现的,默认情况下MEF会用该部件所在容器(也就是根容器)的上下文来解析它的依赖,而不是你发起解析请求的子容器。所以哪怕你在子容器里覆盖了MyPriceProvider,MyCalculator还是会从根容器拿那个共享实例。
下面给你两个实用的解决方案,都不需要修改根容器,完全满足你复用根容器创建多子容器的需求:
方案1:用ExportFactory在子容器上下文创建实例
这是最简洁的方案,不用改任何现有部件的代码,只需要调整子容器里解析MyCalculator的方式:
首先确保你的MEF版本支持ExportFactory(.NET Framework 4.0+、.NET Core 3.0+都没问题),然后把原来直接解析MyCalculator的代码换成下面这样:
// 替换子容器中解析MyCalculator的代码 var calculatorFactory = childContainer.GetExportedValue<ExportFactory<MyCalculator>>(); var calculatorFromChildProvider = calculatorFactory.CreateExport().Value; // 现在验证:calculatorFromChildProvider.PriceProvider 就是你在子容器里覆盖的实例 if (calculatorFromChildProvider.PriceProvider == myPriceProvider) { // 符合预期! }
为什么管用:ExportFactory会绑定到你获取它的那个上下文(也就是子容器),当你调用CreateExport()时,MEF会用子容器的上下文来解析MyCalculator的所有依赖,自然会优先用子容器里的MyPriceProvider。
方案2:给子容器加独立的部件目录(适合完全隔离场景)
如果你的场景需要子容器完全独立加载部件,不想和根容器的部件上下文混在一起,可以给子容器单独配置Catalog,让它先从自己的Catalog里找部件,找不到再回退到根容器:
// 创建子容器专属的Catalog,和根容器用同一个程序集目录 var childAggregateCatalog = new AggregateCatalog(); childAggregateCatalog.Catalogs.Add(new DirectoryCatalog(".", "*.exe")); // 配置子容器的CatalogExportProvider,指定SourceProvider为子容器自身 var childCatalogProvider = new CatalogExportProvider(childAggregateCatalog); var childContainer = new CompositionContainer(childCatalogProvider, rootContainer); childCatalogProvider.SourceProvider = childContainer; // 覆盖子容器里的MyPriceProvider var myPriceProvider = new MyPriceProvider(); childContainer.ComposeExportedValue(myPriceProvider); // 现在直接解析MyCalculator就行 var calculatorFromChildProvider = childContainer.GetExportedValue<MyCalculator>();
原理:子容器会先从自己的Catalog里找到MyCalculator的定义,这时部件的上下文就是子容器,解析依赖时自然会用子容器里的MyPriceProvider。
小提醒
- 方案1更轻量,适合绝大多数场景,不需要额外配置Catalog
- 方案2适合需要完全隔离部件加载的场景,虽然会重复加载程序集里的部件定义,但因为
MyCalculator是NonShared的,不会有实例冲突 - 千万别改根容器里
MyPriceProvider的Shared特性,不然会破坏根容器的共享语义,影响其他使用根容器的场景
内容的提问来源于stack exchange,提问作者Eirik Fauske

