Why doesn’t MEF support open-generics for exports? Because MEF is not type based.


Update: We’ve posted an extension for doing open generics in MEF contrib. Read about it here: http://blogs.msdn.com/gblock/archive/2009/08/20/open-generic-support-in-mef-contrib.aspx


I get this question all the time. Recently it popped it’s head on the forums in this thread (which I referred to in my last post)


In that thread, Andrew was asking why the following does not work?



[Export(typeof(IDomainController<>)), CompositionOptions(CreationPolicy = CreationPolicy.Shared)]
public class DomainController<T> : IDomainController<T>
{
  /* some code here */
  [ImportingConstructor]
  public DomainController(IRepository repository)
  {
 
    _Repository = repository
  }
} 

That is he wants to export a generic IDomainController with the idea the closed generic versions like IDomainController<Customer> could be imported.


He also rightly observed that many of the IoC containers that exist support this functionality including Unity. (I chuckled when I heard that because I was in p&p when we first wrote Unity) However, it is a valid question, why don’t we support it? This is one of those questions that I, Hammett and Nick all asked when we joined the team.  We do support closed generics without a problem, but not open. Why?


As you dig into the details, it quickly becomes clear. The root of the answer is that MEF is not type based, it is contract based. Below are the details from my response…


MEF parts relate on contracts which are strings, not types. To illustrate, see the code below.



namespace Orders {
  public interface IOrderRepository {}
 
  [Export(typeof(IOrderRespository))]
  public class OrderRepository : IOrderRepository {
  }
}

Although I have passed typeof(IOrderRepository) to the export, that is just a convenience that gets converted to a contract name of “Orders.IOrderRepository”. The same applies to the importer…



[Export]
public class OrderService(
  [Import]
  public IOrderRepository Repository {gets;set;} 
)

The import on IOrderRepository also gets converted to a contract name of “Orders.IOrderRepository”. During composition, the container is able to satisfy the import as the 2 contracts match. In the same way we support closed generics, so this code….   



public interface IRepository<T> {}
 
[Export(typeof(IRepository<Order>))]
public class OrderRepository : IRepository<Order> {
}
 
[Export]
public class OrderService(
  [Import]
  public IRepository<Order> Repository {gets;set;} 
)

will work because the OrderRepository is exporting the contract name “Orders.IRepository<Order>” and the OrderService is importing the same contract.


However, this is what it looks like if we try the same with open generics.



public interface IRepository<T> {}
 
namespace Utils {
  [Export(typeof(IRepository<>))]
  public class Repository : IRepository<T> {
  }
}
 
[Export]
public class OrderService(
  [Import]
  public IRepository<Order> Repository {gets;set;} 
)

Now the contract names will be different. The exporter will have a contract of  “Utils.IRepository<>” and the importer will have a contract of “Utils.IRepository<Order>”.


It is a simple match up, that breaks down in the open-generics case. This is because fundamentally, MEF is not matching on type.


There are theoretical ways to address it, but they are all very ugly, require a lot of string parsing and really go against the grain of MEF’s core design.

Comments (2)

  1. GuyWithFollowupQuestion says:

    Can you explain (here or another post) why MEF is string-based and not type-based then?

    Presumably there was a design decision made based on pros/cons.

    IOW, one could look at the open-generics case and this post and conclude that MEF was brokenly based on strings as contracts and will stay that way due to a desire not to overhaul the codebase rather than it being the right thing to do, even as it promotes into FX4.  If that’s not the case, a "pro-string-based" post would help clarify why it was the right design decision 🙂

  2. gblock says:

    MEF uses strings as contracts because parts are not dependent on the CLR type system. I can for example have DLR parts which import other DLR parts where type is irrelavent. I could also represent parts in other data formats like XAML.

    Actually contract is just part of the equation, under the hood MEF has a primitives layer that uses constraints to represent imports. The contract is included as just one part of the constraint, but the contraint itself is much richer.

    Our design goals for V1 were around supporting openly extensible systems. Open generics was on that list, but it was much lower on the priority stack. You have to cut somewhere.

    We are looking at open generics in the future. You might be interested in ths post which illustrates a way to support open generics today.

    http://blogs.msdn.com/gblock/archive/2009/08/20/open-generic-support-in-mef-contrib.aspx

    Regards