问题
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
回答1:
Try marking the constructor with the [ImportingConstructor] attribute.
using System.Composition;
...
[ImportingConstructor]
public Logger(ISetting settings)
{
this.settings = settings;
}
回答2:
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
来源:https://stackoverflow.com/questions/38797527/default-constructor-and-open-generics-in-mef-2-using-conventions