3

I am using MEF to compose exported types from several assemblies. I'm using a base class, which should ImportMany dependencies, as specified in the derived classes. It looks something like this:

Base Assembly:

public abstract class BaseClass
{
    [ImportMany(typeof(IDependency)]
    public IEnumerable<IDependency> Dependencies { get; private set; }

    protected BaseClass()
    {
        var catalog = GetCatalog();
        var container = new CompositionContainer(catalog);
        container.ComposeParts(this);
    }

    protected abstract ComposablePartCatalog GetCatalog();
}

Assembly A:

[Export(typeof(BaseClass))]
public class MyA : BaseClass
{
    protected override ComposablePartCatalog GetCatalog()
    {
        return new AssemblyCatalog(Assembly.GetExecutingAssembly());
    }
}

[Export(typeof(IDependency)]
public class DependencyA1 : IDependency {}

[Export(typeof(IDependency)]
public class DependencyA2 : IDependency {}

Assembly B:

[Export(typeof(BaseClass))]
public class MyB : BaseClass
{
    protected override ComposablePartCatalog GetCatalog()
    {
        return new AssemblyCatalog(Assembly.GetExecutingAssembly());
    }
}

[Export(typeof(IDependency)]
public class DependencyB1 : IDependency {}

[Export(typeof(IDependency)]
public class DependencyB2 : IDependency {}

I then compose everything in the base assembly:

static void Main(string[] args)
{
    DirectoryCatalog catalog = new DirectoryCatalog(path, "*.dll");
    var container = new CompositionContainer(catalog);
    IEnumerable<BaseClass> values = container.GetExportedValues<BaseClass>();

    // both BaseClass instances now have 4 Dependencies - from both Assemby A and Assembly B!
}

The problem I encounter, that when I use MEF to compose both MyA and MyB, each contain exported IDependency-ies from both assemblies! I only want MyA to contain exports DependencyA1 and DependencyA2, same with MyB.

I know I probably should be using a dependency injection container for this, but I was hoping it's possible to do with MEF?

Igal Tabachnik
  • 31,174
  • 15
  • 92
  • 157
  • Just a few notes - doing composition with a constructor - I've seen this quite a lot and it's not a great design idea. It would be recommended to have a single part of your application handle everything to do with your composition container, this is known as the *CompositionRoot*. Ideally, your components should be designed to be agnostic of any Extensibility or IoC container, so you can build and test them in isolation. To further this, although spinning up a new instance of a `CompositionContainer` is relatively cheap, catalog creation can be expensive. – Matthew Abbott Aug 28 '12 at 07:10
  • Another point to consider, is that you're making a call to a virtual method from your constructor, which means you can no longer guarantee that your `BaseClass` constructor will ever complete, because it now relies on a call back up the inheritance hierarchy. If your `override` method ever attempts to use instance members of your subclass type - you may end up with `NullReferenceException` instances being thrown because those instance members might not have even been initialised yet. – Matthew Abbott Aug 28 '12 at 07:13
  • @MatthewAbbott RE virtual call in constructor, you're right, this is just for this example. In my "real" app, I'm not calling the virtual method from the constructor. – Igal Tabachnik Aug 28 '12 at 07:56
  • @MatthewAbbott Regarding your first point, I agree with typical use of a composition root. In this case I'm somewhat (ab)using MEF as a DI container, having it compose its own types, while still exposing the contracts, to be composed later by the main app. – Igal Tabachnik Aug 28 '12 at 07:58

2 Answers2

1

Doing a composition under the nose of another composition is quite nasty ;).

So I would resolve on calling container.GetExportedValues manually and set the property by yourself in the constructor that way you get rid of the [ImportMany] all together. and it is not being manipulated on the outer composition.

HTH Ariel

ArielBH
  • 1,991
  • 3
  • 22
  • 38
  • Excellent! I removed the `ImportMany` attribute, and replaced it with a simple call to `GetExportedValues`. This solves the problem! Thanks mate! – Igal Tabachnik Aug 26 '12 at 19:50
1

You are doing some strange dancing around MEF with this approach you are essentially composing BaseClass multiple times which will give you different results based on which composition happens last. The way the code is currently written the composition that happens in Main will be the one setting the ImportMany last because that happens after the constructor call to BaseClass. Similar to Ariel I would advise against you doing multiple compositions on the same object.

If you must do something like this with MEF here are a few ways I can see it possibly working: 1) Don't do the second composition in the constructor but instead use IPartImportsSatisfiedNotification and in OnImportsSatisifed do the second composistion, although watch out for the second call to this method when you compose the second time. 2) Do what Ariel suggested and don't use ImportMany and instead simply do a GetExportedValues to set that field with the other Assembly level catalog. Do keep in mind that is still making the big assumption that you are only going to have one derived class per assembly otherwise you will still hit overlaps. 3) You could move the ImportMany into the derived class and import a unique dependency type for each derived type (i.e. IADependency or IBDendency). 4) You could use metadata to filter the imports for the specific derived type.

I'm not sure any of these are perfect but if I were to pick an option I would likely go with some variation of #4 and use metadata to filter between the imports. See How does MEF determine the order of its imports? which shows how to order imports but a similar code sample exists for filtering them.

Community
  • 1
  • 1
Wes Haggard
  • 4,284
  • 1
  • 22
  • 21