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?
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; }
}