Resolving closed types with MEF by Mark Seemann
A while back I posed the challenge of resolving closed types with MEF. I received some responses, but I also wanted to provide an alternative outline for a solution. In case you don't remember the problem statement, it revolved around using the Managed Extensibility Framework (MEF) to compose classes in those cases where it's impossible to annotate those classes with the MEF attributes. In the given example I want to compose the Mayonnaise class from EggYolk and OliveOil, but all three classes are sealed and cannot be recompiled.
As I describe in my book, a general solution to this type of problem is to create a sort of adapter the exports the closed type via a read-only attribute, like these EggYolkAdapter and MayonnaiseAdapter classes (the OliveOilAdapter looks just like the EggYolkAdapter):
public class EggYolkAdapter { private readonly EggYolk eggYolk; public EggYolkAdapter() { this.eggYolk = new EggYolk(); } [Export] public virtual EggYolk EggYolk { get { return this.eggYolk; } } } public class MayonnaiseAdapter { private readonly Mayonnaise mayo; [ImportingConstructor] public MayonnaiseAdapter( EggYolk yolk, OliveOil oil) { if (yolk == null) { throw new ArgumentNullException("yolk"); } if (oil == null) { throw new ArgumentNullException("oil"); } this.mayo = new Mayonnaise(yolk, oil); } [Export] public virtual Mayonnaise Mayonnaise { get { return this.mayo; } } }
Doing it like this is always possible, but if you have a lot of types that you need to compose, it becomes tedious having to define a lot of similar adapters. Fortunately, we can take it a step further and generalize the idea of a MEF adapter to a small set of generic classes.
The EggYolkAdapter can be generalized as follows:
public class MefAdapter<T> where T : new() { private readonly T export; public MefAdapter() { this.export = new T(); } [Export] public virtual T Export { get { return this.export; } } }
Notice that I've more or less just replaced the EggYolk class with a type argument (T). However, I also had to add the generic new() constraint, which is often quite restrictive. However, to support a type like Mayonnaise, I can create another, similar generic MEF adapter like this:
public class MefAdapter<T1, T2, TResult> { private readonly static Func<T1, T2, TResult> createExport = FuncFactory.Create<T1, T2, TResult>(); private readonly TResult export; [ImportingConstructor] public MefAdapter(T1 arg1, T2 arg2) { this.export = createExport(arg1, arg2); } [Export] public virtual TResult Export { get { return this.export; } } }
The major difference from the simple MefAdapter<T> is that we need slightly more complicated code to invoke the constructor of TResult, which is expected to take two constructor arguments of types T1 and T2. This work is delegated to a FuncFactory that builds and compiles the appropriate delegate using an expression tree:
internal static Func<T1, T2, TResult> Create<T1, T2, TResult>() { var arg1Exp = Expression.Parameter(typeof(T1), "arg1"); var arg2Exp = Expression.Parameter(typeof(T2), "arg2"); var ctorInfo = typeof(TResult).GetConstructor(new[] { typeof(T1), typeof(T2) }); var ctorExp = Expression.New(ctorInfo, arg1Exp, arg2Exp); return Expression.Lambda<Func<T1, T2, TResult>>( ctorExp, arg1Exp, arg2Exp).Compile(); }
With a couple of MEF adapters, I can now compose a MEF Catalog almost like I can with a real DI Container, and resolve the Mayonnaise class:
var catalog = new TypeCatalog( typeof(MefAdapter<OliveOil>), typeof(MefAdapter<EggYolk>), typeof(MefAdapter<EggYolk, OliveOil, Mayonnaise>) ); var container = new CompositionContainer(catalog); var mayo = container.GetExportedValue<Mayonnaise>();
If you want to change the Creation Policy to NonShared, you can derive from the MefAdapter classes and annotate them with [PartCreationPolicy] attributes.
I'd never voluntarily choose to use MEF like this, but if I was stuck with MEF in a project and had to use it like a DI Container, I'd do something like this.
Comments