Search code examples
.netwpfprismmef

MEF how to get non-shared instance references


Recently, I encountered a problem when I use MEF in my WPF application. I created several classes like below. Part type was set to be CreationPolicy.NonShared so that there will be 2 different object imported to ClassA and ClassB.

[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Part
{
    public int Id { get; set; }
}

[Export]
public class ClassA
{
    [Import]
    public Part PartA { get; set; }
}

[Export]
public class ClassB
{
    [Import]
    public Part PartB { get; set; }
}

And I wrote a little piece of code to describe my problem below.

[Export]
class Program
{
    [Import]
    public ClassA A { get; set; }

    [Import]
    public ClassB B { get; set; }

    [ImportMany(AllowRecomposition = true)]
    public IEnumerable<Part> AllParts { get; set; }

    static void Main(string[] args)
    {
        var catalog = new AssemblyCatalog(typeof(Program).Assembly);
        var container = new CompositionContainer(catalog);

        var prog = container.GetExportedValue<Program>();

        foreach (var part in prog.AllParts)
        {
            // Do something for Part instances.
            // I want to get all Part instances created by MEF which have imported to ClassA and ClassB.
            // However, it comes a list with a brand new Part instance.
        }
    }
}

So there will be ClassA, ClassB and a list of Part intances imported to Program object. Actually, what I wanted to get is all the Part intances that MEF container created. However, it comes a list with a brand new Part instance.

I understand it may because of CreationPolicy.NonShared I specified to Part class. But even I try to find them in container.Catalog.Parts and I also found only one Part instance in it. It makes me confused. From my understanding, the container should hold all references of the objects it created since I already specified AllowRecomposition = true. I found an article to prove this. It says:

Container and parts references

We believe that .Net Garbage Collector is the best thing to rely on for proper clean up. However, we also need to provider a container that has a deterministic behavior. Thus, the container will not hold references to parts it creates unless one of the following is true:

  • The part is marked as Shared
  • The part implements IDisposable
  • One or more imports is configured to allow recomposition

For those cases, a part reference is kept. Combined with the fact that you can have non shared parts and keep requesting those from the container then memory demand can quickly become an issue. In order to mitigate this issue you should rely on one of the following strategies discussed in the next two topics.

So I have 2 questions:

  1. Why I cannot find more than one Part type instances in container?

  2. How can I get all exported Part instances in my demo?


Solution

  • ImportMany doesn't do what you think it does.

    Consider the following:

    public interface IMyInterface
    {
        int Id { get; }
    }
    
    [Export(typeof(IMyInterface))]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class Part1 : IMyInterface
    {
        public int Id { get; private set; }
    }
    
    [Export(typeof(IMyInterface))]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class Part2 : IMyInterface
    {
        public int Id { get; private set; }
    }
    
    [Export]
    public class ClassA
    {
        [ImportMany]
        public IEnumerable<IMyInterface> Parts { get; set; }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            var catalog = new AssemblyCatalog(typeof(Program).Assembly);
            var container = new CompositionContainer(catalog);
    
            var a = container.GetExportedValue<ClassA>();
            // ...
        }
    }
    

    At this point, the a.Parts property will contain two instances: one Part1 and one Part2.

    So the purpose ImportMany is not for you to get all previously exported instances, but to get a new instance of each export that exports your interface.

    As to your question of how to get all exported non-shared instances, I don't believe it is possible, since that is the whole point of it being non-shared.