Search code examples
c#dependency-injectionmef

MEF-Import into Dictionary


Iam currently refactoring a application and want to introduce MEF. The Export class (class Apple) is finished and marked with Export-keyword... On the import site I currently have a dictionary which is initialized as shown below:

Dictionary<int, Apple> dict = new Dictionary<int, Apple>();
for(int i=0; i < 10; i++)
    dict.add(i, new Apple());

...

How can I initialize the dictionary using MEF?


Solution

  • So I think your question boils down to, "How to I ensure that the container produces multiple instances of an object rather than reusing the same object every time it's requested." Well that's pretty easy–you just have to specify CreationPolicy.NonShared.

    Consider this example implementation of IApple:

    public interface IApple { }
    
    [PartCreationPolicy(CreationPolicy.NonShared)]
    [Export(typeof(IApple))]
    public class Apple : IApple 
    { 
        private static int appleCounter = 0;
        private int id;
    
        public Apple() 
        {
            this.id = ++appleCounter;
        }
    
        public override string ToString()
        {
            return "Apple #" + this.id.ToString();
        }
    }
    

    Here's one way that you might use it:

    class Program
    {
        public static void Main(string[] args)
        {
            var catalog = new ApplicationCatalog();
            var container = new CompositionContainer(catalog);
            IDictionary<int, IApple> dict = new Dictionary<int, IApple>();
            for (int i = 0; i < 10; i++)
            {
                dict.Add(i, container.GetExportedValue<IApple>());
            }
    
            foreach (var pair in dict)
            {
                Console.WriteLine("{0}: {1}", pair.Key, pair.Value);
            }
        }
    }
    

    The key line of code here is [PartCreationPolicy(CreationPolicy.NonShared)]. Without this, the only one Apple would ever be created. Of course this isn't quite as useful as you're probably hoping for. Here's another way to generate a dictionary that's a bit more flexible:

    public interface IBasket
    {
        IDictionary<int, IApple> GetAppleDictionary();
    }
    
    [Export(typeof(IBasket))]
    public class Basket : IBasket
    {
        private IDictionary<int, IApple> dict;
    
        [ImportingConstructor]
        public Basket([Import] CompositionContainer container)
        {
            this.dict = new Dictionary<int, IApple>();
            for (int i = 0; i < 10; i++)
            {
                this.dict.Add(i, container.GetExportedValue<IApple>());
            }
        }
    
        public IDictionary<int, IApple> GetAppleDictionary()
        {
            return dict;
        }
    }
    
    class Program
    {
        [Import(typeof(IBasket))]
        private IBasket basket = null;
    
        public static void Main(string[] args)
        {
            var program = new Program();
            program.Run();
        }
    
        private void Run()
        {
            var catalog = new ApplicationCatalog();
            var container = CreateCompositionContainer(catalog);
            container.ComposeParts(this);
            foreach (var pair in this.basket.GetAppleDictionary())
            {
                Console.WriteLine("{0}: {1}", pair.Key, pair.Value);
            }
        }
    
        private static CompositionContainer CreateCompositionContainer(ComposablePartCatalog catalog)
        {
            var wrappedCatalog = new AggregateCatalog(catalog, new TypeCatalog(typeof (CompositionContainer)));
            var container = new CompositionContainer(wrappedCatalog);
            container.ComposeExportedValue(container);
    
            return container;
        }
    }
    

    The tricky part here is CreateCompositionContainer. This method ensures that the the CompositionContainer itself can be used to satisfy an import on an object it's composing. This allows the Basket to directly manipulate the container to generate all the apples it needs.

    And just for the purpose of demonstration, here's one way you could also use the [ImportMany] attribute to accomplish something similar (although all those [Export]'s really make me cringe):

    public interface IApple { }
    
    [PartCreationPolicy(CreationPolicy.NonShared)]
    [Export(typeof(IApple))]
    [Export(typeof(IApple))]
    /* ..repeat N times.. */
    [Export(typeof(IApple))]
    public class Apple : IApple 
    { 
        private static int appleCounter = 0;
        private int id;
    
        public Apple() 
        {
            this.id = ++appleCounter;
        }
    
        public override string ToString()
        {
            return "Apple #" + this.id.ToString();
        }
    }
    
    class Program
    {
        [ImportMany(typeof(IApple))]
        private IEnumerable<IApple> apples = null;
    
        public static void Main(string[] args)
        {
            var program = new Program();
            program.Run();
        }
    
        void Run()
        {
            var catalog = new AssemblyCatalog(this.GetType().Assembly);
            var container = new CompositionContainer(catalog);
            container.ComposeParts(this);
            apples.Dump();
        }
    }