Search code examples
c#genericsmef

MEF 2, Generic Import with implementing class


I am trying to leverage the benefits of Generics with and MEF solution, however I am struggling to get things to register. (Am using MEF 2, .Net 4.51).

Here is a simplified version of my classes and interfaces that I would like to work:

public interface IAnimal
{
    string Speak();
}

public interface IAnimalCatcher<T> where T : IAnimal
{
    string WhatICatch();
    T AnimalCaught { get; set; }
}

[Export(typeof(IAnimal))]
public class Dog : IAnimal
{
    public string Speak()
    {
        return "Woof";
    }
}

//[Export(typeof(IAnimalCatcher<IAnimal>))]
//[Export(typeof(IAnimalCatcher<Dog>))]
public class DogCatcher: IAnimalCatcher<Dog>
{
    public string WhatICatch()
    {
        return "Dogs";
    }

    public Dog AnimalCaught { get; set; }
}

and the code which does the composition:

 public class Program
    {

        private CompositionContainer _container;

        [Import(typeof(IAnimal))]
        public IAnimal TheAnimal;


        [Import(typeof(IAnimalCatcher<>))]
        public IAnimalCatcher<IAnimal> TheAnimalCatcher;

         public Program()
        {
            var catalog = new AggregateCatalog();

            catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));

            _container = new CompositionContainer(catalog);
            this._container.ComposeParts(this);

        }

    }

I cannot get the Dog Class to export and import correctly. I have tried several combination without success (Commented above the classes).

The error is as follows:

System.ComponentModel.Composition.ChangeRejectedException was unhandled Message=The composition remains unchanged. The changes were rejected because of the following error(s): The composition produced a single composition error. The root cause is provided below. Review the CompositionException.Errors property for more detailed information.

1) No exports were found that match the constraint: ContractName MEFGeneric.IAnimalCatcher(MEFGeneric.IAnimal) RequiredTypeIdentity MEFGeneric.IAnimalCatcher(MEFGeneric.IAnimal)

Resulting in: Cannot set import 'MEFGeneric.Program.TheAnimalCatcher (ContractName="MEFGeneric.IAnimalCatcher(MEFGeneric.IAnimal)")' on part 'MEFGeneric.Program'. Element: MEFGeneric.Program.TheAnimalCatcher (ContractName="MEFGeneric.IAnimalCatcher(MEFGeneric.IAnimal)") --> MEFGeneric.Program

Note that IAnimalCatcher<IAnimal> will register, but that doesn't let me limit the dog catcher to dogs.

Is there a way to address this through importing, or is my design flawed, and should I be implementing DogCatcher differently?


Solution

  • MEF is looking for a type that exports IAnimalCatcher<IAnimal>. You have DogCatcher that exports IAnimalCatcher<Dog> so that is not a suitable candidate. Changing the export of DogCatcher to IAnimalCatcher<IAnimal> is not a solution because you cannot assign IAnimalCatcher<Dog> to IAnimalCatcher<IAnimal>.

    If you were able to make the assignment you could then assign AnimalCaught of DogCatcher to Cat by setting the value of theIAnimalCatcher.AnimalCaught property of type IAnimal. You do not want DogCatcher to catch a Cat so the compiler will not allow it.

    The solution is to change the DogCatcher so it implements IAnimalCatcher<IAnimal> and not IAnimalCatcher<Dog>:

    [Export(typeof(IAnimalCatcher<IAnimal>))]
    public class DogCatcher: IAnimalCatcher<IAnimal>
    {
        public string WhatICatch()
        {
            return "Dogs";
        }
    
        public IAnimal AnimalCaught { get; set; }
    }
    

    The unfortunate side effect of this is that you now allow anybody to modify the animal caught by DogCatcher to say a Cat.

    But there is another option. If you can remove the setter from AnimalCaught you can make the IAnimal type parameter covariant (the out modifier):

    public interface IAnimalCatcher<out T> where T : IAnimal
    {
        string WhatICatch();
        T AnimalCaught { get; }
    }
    

    This makes the assignment of IAnimalCatcher<Dog> to IAnimalCatcher<IAnimal> legal:

    [Export(typeof(IAnimalCatcher<IAnimal>))]
    public class DogCatcher: IAnimalCatcher<Dog>
    {
        public string WhatICatch()
        {
            return "Dogs";
        }
    
        public Dog AnimalCaught { get; private set; }
    }