C# 如何使用MEF允许插件覆盖现有功能?

C# 如何使用MEF允许插件覆盖现有功能?,c#,mef,C#,Mef,我使用MEF允许用户扩展我的C#库。到目前为止,它工作得很好,但现在我正试图以一种我以前从未见过的方式使用它 到目前为止,我看到的MEF的主要用例是: 应用程序公开基本接口(IPerson) 外部库使用MEF和基本接口来扩展主库的功能(例如,IPoliceman:IPerson,添加功能) 应用程序然后使用ImportMany搜索正确的IPerson,具体取决于它必须执行的操作 但我需要这样的东西:假设我有一个税收计算器,它接受一系列参数,并根据这些参数返回估计的税收。我希望用户能够使用ME

我使用MEF允许用户扩展我的C#库。到目前为止,它工作得很好,但现在我正试图以一种我以前从未见过的方式使用它

到目前为止,我看到的MEF的主要用例是:

  • 应用程序公开基本接口(
    IPerson
  • 外部库使用MEF和基本接口来扩展主库的功能(例如,
    IPoliceman:IPerson
    ,添加功能)
  • 应用程序然后使用
    ImportMany
    搜索正确的
    IPerson
    ,具体取决于它必须执行的操作
但我需要这样的东西:假设我有一个税收计算器,它接受一系列参数,并根据这些参数返回估计的税收。我希望用户能够使用MEF创建插件,修改这些计算的方式。一次只能加载一个插件。否则,我如何决定使用哪个替代实现


所以基本上,我的问题归结为:通常MEF允许添加类和方法的实现。我如何使用它来允许用户替换一个实现?

您所说的实际上只是以不同的方式看待同一个问题。答案比听起来要简单——对于您希望客户端能够覆盖的任何行为,只需将该行为放入插件中即可


没有任何东西说你不能仅仅因为你是应用程序的作者就写插件。将TaxCalculator类放入插件中,并公开一个允许用户编写自己的税务计算器的接口。在运行时,如果加载了多个,请选择不属于您的。开箱即用,您将使用您的税务计算器插件,因此它将完全按照您期望的方式工作。如果用户创建了自己的税务计算器插件并将其放到正确的目录中,您就可以使用它,从而有效地让他们“覆盖”您的原始功能。

我不确定这会有多大意义,但让我试试

我将创建一个
TaxCalculatorManager
类。该类可以从MEF加载所有
ITaxCalculator
实现。从那里,您可以在
Export
属性中获得一些内容,以允许对实现进行排序。然后,当您需要计算税款时,您将调用
TaxCalculatorManager.calculate
,它将对
ITaxCalculator
实现进行排序,并调用
calculate
对获胜者进行排序


如果您需要我澄清任何要点,请告诉我。

通常,当您尝试覆盖应用程序中已存在的导出时,您将获得
[Import(typeof(IFoo)]
的基数异常,因为MEF希望正好有一个匹配的导出可用

但是,您可以将插件放在单独的导出提供程序中,并赋予其优先级。我在这里为应用程序文件夹中的“插件”子文件夹执行此操作:

Assembly executingAssembly = Assembly.GetExecutingAssembly();
string exeLocation = Path.GetDirectoryName(executingAssembly.Location);
string pluginPath = Path.Combine(exeLocation, "plugins");

var pluginCatalog = new DirectoryCatalog(pluginPath);
var pluginExportProvider = new CatalogExportProvider(pluginCatalog);

var appCatalog = new DirectoryCatalog(exeLocation,"*");
var appExportProvider = new CatalogExportProvider(appCatalog);

var container = new CompositionContainer(
    pluginExportProvider, appExportProvider);

pluginExportProvider.SourceProvider = container;
appExportProvider.SourceProvider = container;

传递到合成容器的导出提供程序的顺序决定了优先级:如果导出同时由插件和应用程序部件提供,则插件将获得优先级。

这似乎非常简单。只需允许加载一个插件,如果没有可用插件,则使用默认实现。然后,您可以使用用户在您的配置中选择。这很有意义,谢谢!如果加载两个插件试图覆盖同一个实现,会发生什么情况。我应该让每个插件覆盖彼此的更改,还是应该引发异常。如果我确实引发异常,我如何首先检测冲突?您希望如何处理它在您的业务案例中添加例外。有时例外是个好主意,有时您只想选择一个例外。加载程序集时可以检测到冲突。如果有多个程序集导出您的ITaxCollector接口(即使在从列表中删除您的程序集后),您知道存在冲突。好的,谢谢。我必须考虑一下异常与选择异常之间的关系。谢谢您的帮助!我想您是说使用
[ImportMany]
无处不在,以及优先选择非默认部分的代码。或者,您建议使用可选的导入和
AllowDefault=true
,如果导入为
null
,则返回默认值。无论哪种方式,这对我来说都有点麻烦。您可以使用
[import]
并在容器设置中构建插件的首选项,如我自己的回答所示。正如Fuji所指出的,如果存在多个实现,您可以允许用户选择获胜者。感谢您的回答。我喜欢按重要性对实现进行排序的想法……我会研究它。