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

Is there anyway we can enhance your MefAdapter to support keys as well? So that when the adapter is export's using the Export property we could associate a key to the export? With attributes I know the string has to be constant, didn't know if we could somehow assign this in runtime even though.
2012-09-19 23:12 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or Google Plus, or somewhere else with a permalink. Ping me with the link, and I may add it as a comment.

Published

Monday, 14 March 2011 20:49:11 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!