默认构造函数和MEF2中使用约定的开放泛型

本文关键字:约定 泛型 构造函数 MEF2 默认 | 更新日期: 2023-09-27 18:01:14

我试图在我的项目中使用MEF2,我通常使用SimpleInjector,但这次我想尝试MEF。我的主要问题是处理开放泛型,这就是我到目前为止得到的

public interface ISetting {}
public class Setting : ISetting {}
public interface ILogger<TLog>
{
    TLog Fetch()
}
public class Logger<TLog> : ILogger<TLog>
{
    private ISetting settings;
    public Logger(ISetting settings)
    {
        this.settings = settings;
    }
    public TLog Fetch()
    {
        return default(TLog);
    }
}

现在对于容器部分,我做

var conventions = new ConventionBuilder();
conventions.ForType<Setting>()
           .Export<ISetting>()
           .Shared();
conventions.ForType(typeof(Logger<>))
           .Export(t => t.AsContractType(typeof(ILogger<>)))
           .Shared();
var configuration = new ContainerConfiguration()
        .WithAssembly(typeof(Program).Assembly,conventions);
using (var container = configuration.CreateContainer)
{
    var export = container.GetExport<ILogger<object>>(); //Exception :(
}

当它试图检索导出时,我得到了这个异常

在类型"MEFTest.Logger `1[System.Object]"上找不到导入构造函数。

如果我从Logger类中删除构造函数,那么容器构造的封闭泛型就可以了。我有98%的把握这个问题与构造函数有关,但我觉得我在这里遗漏了一些

编辑1:通过阅读,我实际上发现MEF有两个版本,一个是Nuget包,另一个是.NET40附带的,我使用的是Nuget软件包。我做了一些重构以使用.NET40 附带的那个

除了创建和使用容器的部分外,所有代码都是相同的

var category = new AssemblyCatalog(Assembly.GetExecutingAssembly(), conventions);
using (var container = new CompositionContainer(category))
{
    var logic = container.GetExport<ILogger<int>>().Value; //Lazy Initialization O.o
    var test = logic.Fetch();
    // the rest of the code …
}

这很好:(很好,所以我肯定在Nuget包的版本中错过了一些东西

编辑2:通过删除WithAssembly方法中的通用部件的"自动检测">,代码工作,以下是代码重构的

会议部分:

var conventions = new ConventionBuilder();
conventions.ForType<Setting>()
           .Export<ISetting>();
conventions.ForType<Logger<int>>()
           .Export<ILogger<int>>();

集装箱部分:

var types = new Type[] { typeof(Setting), typeof(Logger<int>) };
var configuration = new ContainerConfiguration()
        .WithParts(types, conventions);
using (var container = configuration.CreateContainer())
{
    var logic = container.GetExport<ILogger<int>>();
    var test = logic.Fetch();
    // the rest of the code …
}

我将特定类型更改为integer。当它执行Fetch((时,它正确地返回0作为int 的默认值

有趣的部分是为什么泛型的"自动检测">强制构造函数标记为

编辑3:我认为"自动检测">部分不是这里的故障,因为我已经尝试过这个

var conventions = new ConventionBuilder();
conventions.ForType<Setting>()
           .Export<ISetting>();
conventions.ForType(typeof(Logger<>))
           .Export(t => t.AsContractType(typeof(ILogger<>)));
var types = new Type[] { typeof(Setting), typeof(Logger<>) };
var configuration = new ContainerConfiguration()
        .WithParts(types, conventions);
using (var container = configuration.CreateContainer())
{
    var logic = container.GetExport<ILogger<int>>();
    var test = logic.Fetch();
    // the rest of the code …
}

有了这段代码,我又回到了原点,因为它产生了相同的异常,它强制使用标记属性

编辑4:实际的MEF项目已进入System.Composition下的CoreFx GitHub页面。我进入了单元测试和RegistrationBuilderCompatilityTest第40-58行

public interface IRepository<T> { }
public class EFRepository<T> : IRepository<T> { }
[Fact]
public void ConventionBuilderExportsOpenGenerics()
{
    var rb = new ConventionBuilder();
    rb.ForTypesDerivedFrom(typeof(IRepository<>))
      .Export(eb => eb.AsContractType(typeof(IRepository<>)));
    var c = new ContainerConfiguration()
        .WithPart(typeof(EFRepository<>), rb)
        .CreateContainer();
    var r = c.GetExport<IRepository<string>>();
}

他们从未在没有默认构造函数的情况下测试过它

结果:我最终在CoreFx Github页面上打开了一个问题,并提交了一份修复了错误的PR

默认构造函数和MEF2中使用约定的开放泛型

尝试用[ImportingConstructor]属性标记构造函数。

using System.Composition;
...
[ImportingConstructor]
public Logger(ISetting settings)
{
    this.settings = settings;
}

在他们合并我的PR并发布修复了错误的版本之前,我建议使用编辑2上的解决方案。我认为这是实现所需功能的侵入性最小的方法

基本上,您需要使用CLOSED泛型类型构建约定,并使用这些封闭泛型创建集合。在容器中,使用WithParts方法以及定义的约定,有关代码段,请参见Edit 2

更新

我的PR已经合并,现在支持此功能。它将于2017年4月30日在corefx 2.0中发布。