MEF:有什么方法可以在 class 构建之前有条件地 select 导出?

MEF: Any way to conditionally select an export prior to class construction?

我有两个 ISchedulerProvider 接口的实现,一个用于测试目的,一个用于非测试执行(Lee Campbell 的 Intro to Rx 的赞美)。我想通过 MEF 导出这两个实例并处理导入,如果我检测到我在测试环境中,则使用测试调度程序,如果我不在,则使用另一个。

我可以在每次导出时设置元数据,在任何地方使用 [ImportMany] 我使用 ISchedulerProvider 和相应的过滤器,但这看起来开销很大。在 class 构建之前,有条件地 select 出口之间有什么办法吗?

一个简单的例子会很有帮助,特别是如果解决方案涉及出口供应商之类的东西。

我能够通过创建一个继承自 CompositionContainer 的 class 来实现这一点,它根据自定义属性过滤掉导出。

这是我拼凑的一个例子:

要实现的接口

public interface ISchedulerProvider
{
    string Foo { get; }
}

用于保存目标环境信息的自定义属性,我在此示例中使用了字符串,但我可能会在生产代码中使用枚举:

public class EnvironmentSpecificAttribute : Attribute
{
    public string TargetEnvironment { get; }

    public EnvironmentSpecificAttribute(string targetEnvironment)
    {
        TargetEnvironment = targetEnvironment;
    }
}

示例实现:

[EnvironmentSpecific("QA")]
[Export(typeof(ISchedulerProvider))]
public class TestProvider : ISchedulerProvider
{
    public string Foo { get; } = "TestBar";
}

[EnvironmentSpecific("Prod")]
[Export(typeof(ISchedulerProvider))]
public class RealProvider : ISchedulerProvider
{
    public string Foo { get; } = "RealBar";
}

Class 消费接口:

[Export]
public class Consumer
{
    private readonly ISchedulerProvider _schedulerProvider;

    [ImportingConstructor]
    public Consumer(ISchedulerProvider schedulerProvider)
    {
        _schedulerProvider = schedulerProvider;
    }

    public string GetFoo()
    {
        return _schedulerProvider.Foo;
    }
}

自定义组合容器:

public class EnvironmentSpecificContainer : CompositionContainer
{
    private readonly string _targetEnvironment;

    public EnvironmentSpecificContainer(ComposablePartCatalog catalog, string targetEnvironment, params ExportProvider[] providers) : base(catalog, providers)
    {
        _targetEnvironment = targetEnvironment;
    }

    protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
    {
        return base.GetExportsCore(definition, atomicComposition)?.Where(IsForEnvironmentOrEnvironmentNotSpecified);
    }

    private bool IsForEnvironmentOrEnvironmentNotSpecified(Export export)
    {
        EnvironmentSpecificAttribute attribute = export.Value.GetType().GetCustomAttribute<EnvironmentSpecificAttribute>() as EnvironmentSpecificAttribute;
        return attribute == null || string.Equals(attribute.TargetEnvironment, _targetEnvironment,
                   StringComparison.InvariantCultureIgnoreCase);
    }
}

最后是上面classes的用法:

static void Main(string[] args)
{
    string environmentName = "QA";
    EnvironmentSpecificContainer container = new EnvironmentSpecificContainer(new AssemblyCatalog(Assembly.GetCallingAssembly()), environmentName);

    var tmp = container.GetExportedValue<object>("MefTest.Consumer");
}