What is it?

Open-generic support is very simple. It means that you can provide a part with a generic contract, that can be used to satisfy all imports of a closed form of that contract. The canonical example folks use is Repository<T> and IRepository<T>. What I want to be able to do is register a generic Repository such that any imports of IRepository<T> can be satisfied, this IRepository<Customer>, IRepository<Order>, etc can all be handled by a single Repository<T> that is registered in the catalog.

MEF’s attributed model however does not support this, we don’t allow even exporting open-generic types. If you put an export attribute on say Repository<T>, we ignore it. MEF does support closed generic types. For example I can have an importer of IRepository<Customer>, and i can register Repository<Customer> which exports IRepository<Customer>. However, that means that I have to add a specific implementation of IRepository<T> for every repository in existence. This is problematic because the importer doesn’t want to be burdened with having to add these specific implementations, or to know whether or not it even exists.

Enter GenericCatalog

The new GenericCatalog changes that. Generic Catalog is a custom catalog that can both discover open-generic implementations, and create closed implementations on demand. It also supports generic specialization, that is it allows you to register specific implementations of a generic contract, which override the default that will be created by the open-generic. Finally it supports one other requested feature, that is it can create concrete instances that are imported, even if they were not added to any catalog.

GenericCatalog is a decorator. You pass it in it’s constructor all your catalogs, and it sits on top, delegates to the inner catalogs, and intercepts requests for generic types that were not found in the catalog.

How you use it

To see how it works, check out the specification / context below (otherwise known as a unit test)

[TestFixture]
public class When_querying_catalog_for_an_order_repository_and_no_closed_repository_is_present : GenericCatalogContext
{
    [Test]                    
    public void order_repository_part_definition_is_created()
    {
        Assert.IsNotNull(_result.Item1);
    }
 
    [Test]
    public void order_repository_export_is_created()
    {
        Assert.IsNotNull(_result.Item2);
    }
 
    public override void Context()
    {
        _result = _genericCatalog.GetExports(_repositoryImportDefinition).Single();
    }
 
    private Tuple<ComposablePartDefinition, ExportDefinition> _result;
}
Looking at the spec we can see that we are creating the container passing in a generic catalog. We are then asking the container for an IRepository<Order> and we are verifying that we got one.

Notice in the test that we are calling the overload that accepts an ImportDefinition rather than just calling container.GetExportedValue<IRepository<Order>>. MEF creates special kinds of ImportDefinitions when you add an Import to a part. These definitions carry additional information that the generics implementation relies on. When you call GetExport directly on the container however, the definition that is created is a different definition which doe snot carry this information. As such, in order to take advantage of the new functionality, you import the generic type in a part. For example, the IRepository<Order> definition came from this import below.

[Export]
public class OrderProcessor
{
    [Import]
    public IRepository<Order> OrderRepository { get; set; }
}
OrderProcessor is importing IRepository<Order>

How do you setup the container?

In order to setup the container to support open-generics, you create the GenericCatalog passing in all your other catalogs, usually this will be your conventional AggregateCatalog that contains all your catalogs today. For example below in the base context class you can see how we setup the catalog for this test.

public class GenericCatalogContext
{
    protected AggregateCatalog _aggegateCatalog;
    protected GenericCatalog _genericCatalog;
    protected ImportDefinition _repositoryImportDefinition;
 
    public GenericCatalogContext()
    {
        var typeCatalog = new TypeCatalog(typeof(OrderProcessor), typeof(RepositoryTypeLocator));
        _aggegateCatalog = new AggregateCatalog();
        _aggegateCatalog.Catalogs.Add(typeCatalog);
        _genericCatalog = new GenericCatalog(_aggegateCatalog);
        string orderProcessorContract = AttributedModelServices.GetContractName(typeof(OrderProcessor));
        var orderProcessPartDefinition = typeCatalog.Parts.Single(p => p.ExportDefinitions.Any(d => d.ContractName == orderProcessorContract));
        _repositoryImportDefinition = orderProcessPartDefinition.ImportDefinitions.First();
        Context();
    }
 
    public virtual void Context()
    {
        
    }
}
In this case we are creating a type catalog that we are adding our types for our test, an OrderProcessor and a RepositoryTypeLocator (more about that in the next section). Next we are creating an AggregateCatalog, and adding the type catalog to it. Finally we are creating a GenericCatalog and passing it the Aggregate which contains everything else. Next I do a bit of hackery to get the ImportDefinition off of the OrderProcessor in order to do the query in the test. As i mentioned you shouldn’t have to do this, as you’ll be grabbing something from the container that likely depends on the generic import rather than needed it directly.

Type Mapping

If you are following along, you might be asking yourself where are the open-generic types? And that is where GenericTypeMapping comes in. As I mentioned earlier, MEF does not allow exporting / importing open-generics. To work around that, I’ve introduced a non-generic contract that carries generic type information :-) Not only that, but the implementation types passed in actually are open-generic parts!

GenericTypeMapping accepts two parameters in it’s constructor, one is an open-generic contract type, and the other is an open-generic implementation type. This type also is an inherited export, taking advantage of our new Preview 6 feature which thus removes the need for the attribute on derivers.

[InheritedExport]
public abstract class GenericContractTypeMapping
{
    public GenericContractTypeMapping(Type genericContractTypeDefinition, Type genericImplementationTypeDefinition)
    {
    }
 
    public Type GenericContractTypeDefinition { get; }
    public Type GenericImplementationTypeDefinition { get; }
}
To use it, you derive from GenericContractTypeMapping for each open-generic type you want to export. You then make sure that it is in one of the catalogs that is passed in to the GenericCatalog. In our example we have a RepositoryTypeLocator which has a contract of IRepository<> and an implementation of Repository<>.

public class RepositoryTypeLocator : GenericContractTypeMapping
{
    public RepositoryTypeLocator()
        :base(typeof(IRepository<>), typeof(Repository<>))
    {
    }
}
Repository<> is a generic part. It supports constructor injection, can have imports / exports just like any other part.

public class Repository<T> : IRepository<T>
{
}
So all you have to do is create generic parts, and corresponding type mappings, put them in the catalog, and as Karl Shifflett says, “DONE!”

How it works

GenericCatalog is what is doing all the magic here. This guy automatically queries his inner catalog for all GenericTypeMapping contacts. Once he has them, he takes the types within and adds them to a mapping table from generic contract to generic implementation. Whenever the catalog is queried, it will see if any exports were returned, if not and the importing type is generic, it will grab the generic type definition, and lookup in that table built earlier. If it finds that it can match against that definition, it will grab the implementation and create a closed generic type. It will then add the new type to a TypeCatalog, which it will add to it’s inner catalog. Once it does this, it then queries the catalog to grab the new export, returns it, and Voila.

You can dig into the source if you want to know more of the nitty gritty.

Last edited Aug 24, 2009 at 6:42 AM by TheCodeJunkie, version 2

Comments

aph5 Dec 13, 2010 at 2:32 PM 
Error 1 'CommandHandlerTypeLocator': cannot derive from sealed type 'MefContrib.Hosting.Generics.GenericContractTypeMapping' C:\.....\CommandHandlerTypeLocator.cs

fkode87 Jun 16, 2010 at 3:58 PM 
Hello,

I am not being able to get this work, even from the latest SVN of MEFContrib.

The contract name of the Export is always different from the Import one which results in composition error (No valid exports found...)

Export's contract name is "MEFGeneric.IRepository()".
Import's one is "MEFGeneric.IRepository(MEFGeneric.Order)"

Can someone please tell me why this is so?

Note: I am using MEF from .NET 4 RTM.
Thanks.