简体   繁体   中英

Default constructor and open generics in MEF 2 using conventions

I am trying to use MEF 2 in my project, I usually use SimpleInjector but this time I wanted to try MEF. My main problem is dealing with open generics, this is what i got so far

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);
    }
}

Now for the container part I do

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 :(
}

When it's trying to retrieve the export I am getting this exception

No importing constructor was found on type 'MEFTest.Logger`1[System.Object]'.

If I remove the constructor from the Logger class the container construct the closed generic just fine. I am 98% sure that the problem is related with the constructors but I feel I am missing something here

Edit 1: Doing some reading I have actually discovered that there are 2 versions of MEF, one that is a Nuget package and another one that ships with .NET40, the one that I am using is the Nuget package. I did some refactoring to use the one that ships with .NET40

All the code is the same except for the part that creates and use the container

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 …
}

This works :) just fine so definitely I am missing something in the version of the Nuget package

Edit 2: With the removal of the "auto-detection" of the generic parts in the WithAssembly method the code works, here is the code refactored

The convention part:

var conventions = new ConventionBuilder();

conventions.ForType<Setting>()
           .Export<ISetting>();

conventions.ForType<Logger<int>>()
           .Export<ILogger<int>>();

The container part:

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 …
}

I changed the specific type to integer .When it executes Fetch() it returns correctly 0 as the default value for int

The interesting part is why the "auto-detection" of the generics force the constructor to be marked

Edit 3: I think the "auto-detection" part is not the one at fault here because I have tried this

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 …
}

With that code I am back to square one because it produces the same exception, it enforces the use of the marking attribute

Edit 4: The actual MEF project has gone to the CoreFx GitHub page under System.Composition . I went to the unit tests and in the RegistrationBuilderCompatibilityTest lines 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>>();
}

They never tested it without the default constructor

Results: I end up opening an issue on the CoreFx Github page and submitted a PR with a fix for the bug

Try marking the constructor with the [ImportingConstructor] attribute.

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

Until they merge my PR and release a version with the bug fixed I recommend going with the solution on Edit 2 . I think it is the least intrusive way of achieving the functionality required

Basically you need to build conventions with your CLOSED generic types and create a collection with these closed generics. In the container use the WithParts method along with the conventions that were defined, see Edit 2 for code snippet

UPDATE

My PR has been merged and this functionality is now supported. It will be released in corefx 2.0 due by April 30, 2017

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM