tags:

views:

650

answers:

3

I need to add some extension points to our existing code, and I've been looking at MEF as a possible solution. We have an IRandomNumberGenerator interface, with a default implementation (ConcreteRNG) that we would like to be swappable. This sounds like an ideal scenario for MEF, but I've been having problems with the way we instantiate the random number generators. Our current code looks like:

public class Consumer
{
    private List<IRandomNumberGenerator> generators;
    private List<double> seeds;

    public Consumer()
    {
        generators = new List<IRandomNumberGenerator>();
        seeds = new List<double>(new[] {1.0, 2.0, 3.0});

        foreach(var seed in seeds)
        {
            generators.Add(new ConcreteRNG(seed);
        }
    }
}

In other words, the consumer is responsible for instantiating the RNGs it needs, including providing the seed that each instance requires.

What I'd like to do is to have the concrete RNG implementation discovered and instantiated by MEF (using the DirectoryCatalog). I'm not sure how to achieve this. I could expose a Generators property and mark it as an [Import], but how do I provide the required seeds?

Is there some other approach I am missing?

+2  A: 

Currently there isn't a direct way to do this in MEF but the MEF team is considering support for this in v.Next. You essentially want to create multiple instances of the same implementation which is traditially done using a Factory pattern. So one approach you could use is something like:

public interface IRandomNumberGeneratorFactory
{
  IRandomNumberGenerator CreateGenerator(int seed);
}

[Export(typeof(IRandomNumberGeneratorFactory))]
public class ConcreateRNGFactory : IRandomNumberGeneratorFactory
{
  public IRandomNumberGenerator CreateGenerator(int seed)
  {
    return new ConcreateRNG(seed);
  }
}

public class Consumer
{
  [Import(typeof(IRandomNumberGeneratorFactory))]
  private IRandomNumberGeneratorFactory generatorFactory;
  private List<IRandomNumberGenerator> generators;    
  private List<double> seeds;    

  public Consumer()    
  {
    generators = new List<IRandomNumberGenerator>();
    seeds = new List<double>(new[] {1.0, 2.0, 3.0});

    foreach(var seed in seeds)
    {            
      generators.Add(generatorFactory.CreateGenerator(seed));
    }
  }
}
Wes Haggard
Thanks Wes. I had considered a factory approach, but had got stuck due to the fact that I wanted a generic factory that would create an instance of whichever IRandomNumberGenerator type was discovered by MEF.Thinking about it again, your approach doesn't seem like much extra work - thanks again.
Akash
I've got that working now. I simplified it a bit by providing a static factory method on ConcreteRNG:[Export(typeof(Func<double, IRandomNumberGenerator>))]public static readonly Func<double, IRandomNumberGenerator> Create = seed => new ConcreteRNG(seed);
Akash
Yes export a function itself is also another simplified way to get what you want. Also I just realized that if you want to use that import within the constructor then you would need to make it a constructor import, because that import as I demonstrated would not be set before object construction.
Wes Haggard
A: 

I believe this is what the Lazy Exports feature is for. From that page:

[Import]
public Export<IMessageSender> Sender { get; set; }

In this case you are opt-in for delaying this instantiation until you actually need the implementation instance. In order to request the instance, use the method [Export.GetExportedObject()]. Please note that this method will never act as a factory of implementations of T, so calling it multiple times will return the same object instance returned on the first call.

Scott Whitlock
Scott, I need multiple instances of IRandomNumberGenerator. Your comment suggests that I will get the same instance each time. Am I missing something?
Akash
Sorry, I missed that part. In that case I think you need the factory pattern.
Scott Whitlock
+1  A: 

MEF preview 8 has experimental support for this, though it is not yet included in System.ComponentModel.Composition.dll. See this blog post for more information.

You'll have to download the MEF sources and build the solution. In the Samples\DynamicInstantiation folder you'll find the assembly Microsoft.ComponentModel.Composition.DynamicInstantiation.dll. Add a reference to this assembly and add a dynamic instantiation provider to your container like this:

var catalog = new DirectoryCatalog(".");
var dynamicInstantiationProvider  = new DynamicInstantiationProvider();
var container = new CompositionContainer(catalog, dynamicInstantiationProvider);
dynamicInstantiationProvider.SourceProvider = container;

Now your parts will be able to import a PartCreator<Foo> if they need to dynamically create Foo parts. The advantage over writing your own factory class is that this will transparently take care of the imports of Foo, and the imports' imports, etcetera.

edit:

  • in MEF Preview 9 PartCreator was renamed to ExportFactory but it is only included in the silverlight edition.
  • in MEF 2 Preview 2, ExportFactory became included for the desktop edition. So ExportFactory will probably be part of the next .NET framework version after .NET 4.0.
Wim Coenen
Thanks, I'll follow up on that link.
Akash