枚举导出和架构

本文关键字:枚举 | 更新日期: 2023-09-27 18:08:14

我想让我的插件子系统使用mef,但我有几个问题,因为我是新手在csharp和mef (

我想做什么:

  1. 每个插件都可以创建自己的接口IPlugin1, IPlugin2…
  2. 每个接口必须有指定的功能Load, Unload
  3. 使用mef我想枚举所有的导出和调用Load/Unload

问题:

。我的解决方案是好的,如果没有,我如何改进它(通过使用其他东西)?

b。如何枚举所有出口使用mef和调用指定的接口功能?

我将感谢所有的链接和批评。

枚举导出和架构

基于@m-y的回答,我确实认为拥有一个通用的界面是你可以采用的最佳设计。这是利用可应用于插件的一组通用操作的最简单方法。不过,我要考虑的是稍微改进一下:

public interface IPlugin : IDisposable
{
    void Initialise();
}

通过强制Dispose方法的实现,您的部件可以被CompositionContainer的生命周期管理特性自动控制。你所有的卸载代码都可以放在这里,这里是一个示例插件:

public interface ILogger : IPlugin
{
    void Log(string message);
}
[Export(typeof(ILogger))]
public class ConsoleLogger : ILogger
{
    void IPlugin.Initialise()
    {
        Console.WriteLine("Initialising plugin...");
    }
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
    public virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            Console.WriteLine("Disposing plugin...");
        }
    }
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Dispose模式允许一种标准化的机制来清理代码。您的CompositionContainer实例将跟踪此项目,并在处置时将其清除。

现在,我想描述的是MEFContrib中一个很好的添加,称为InterceptingCatalog。这个目录允许你做的是注册拦截策略,它可以让你在输出值返回给调用代码之前访问它。其中一种用法是,在第一次导出实例时,自动确保在基本IPlugin接口上调用Initialise:

public class InitialisePluginStrategy : IExportedValueInterceptor
{
    public object Intercept(object value)
    {
        var plugin = value as IPlugin;
        if (plugin != null)
            plugin.Initialise();
        return value;
    }
}

让我们把这些都绑起来:

static void Main(string[] args)
{
    var catalog = new AssemblyCatalog(typeof(Program).Assembly);
    var configuration = new InterceptionConfiguration()
        .AddInterceptor(new InitialisePluginStrategy());
    var interceptingCatalog = new InterceptingCatalog(catalog, configuration);
    var container = new CompositionContainer(interceptingCatalog);
    var logger = container.GetExportedValue<ILogger>();
    logger.Log("test");
    Console.ReadKey();
}

如果你运行它,你会注意到我们的ConsoleLogger在第一次从容器中获得它时被自动初始化。我们不需要担心它被再次初始化,它只会在创建导出实例时才会这样做,这意味着它遵循单例和非单例场景。

在这一点上,您可能会认为这可能是多余的,但它实际上是一个相当优雅的解决方案,使您的部件能够自动启动,并在不再需要时处理。

当然,如果你想要细粒度地控制你的部件如何初始化,你可以在你自己编写的方法中管理这些,你只需要在这样做时考虑插件的状态。

您可以在Piotr Włodek的博客上阅读有关InterceptingCatalog的更多信息

如果你想让你所有的插件接口都遵循它们自己的接口,那么你应该为它们提供一个扩展接口:

public interface IMyInterface
{
    void Load();
    void Unload();
}

然后,当插头创建自己的接口时,它们应该从您公开提供的接口扩展:

public interface IPlugin1 : IMyInterface
{
    void DoPlugin1Func();
}

现在,为了获得在MEF中导出的接口集合,您必须将接口标记为InheritedExport:

[InheritedExport(typeof(IMyInterface))]
public interface IMyInterface { ... }

这表明任何以某种方式从IMyInterface扩展的类都将被导出为IMyInterface的类型,甚至是向下的树:

//This class will be exported as an IMyInterface
public class PluginImplementation1 : IPlugin1 { ... }

最后,在代码的某处(适用的地方),您可以导入一组IMyInterface实例。

public class SomeClass
{
    [ImportMany(typeof(IMyInterface))]
    private IEnumerable<IMyInterface> Plugins { get; set; }
}

如果一切都连接正确,Plugins将是一个类的枚举,通过继承,导出为IMyInterface s。


资源:

    <
  • InheritedExport例子/gh><
  • ImportMany例子/gh>