Search code examples
c#genericscontravariance

Contravariance generic interface, and a wall I keep running into


I have a class Company that holds a list of different IFactory<IPart>; e.g. an EngineFactory.

public class Company
{
    private Dictionary<Type, IFactory<IPart>> _factories;

    public Company()
    {
        _factories = new Dictionary<Type, IFactory<IPart>>();
        _factories[typeof (Engine)] = new EngineFactory();
    }

    public void SendOrderIntakeToFactory(IPart part)
    {
    }
}

The EngineFactory looks like this:

public class EngineFactory : IFactory<Engine> 
{
    public void Produce(Engine part)
    {
    }
}

And the IFactory interface:

public interface IFactory<T> where T : IPart
{
    void Produce(T part);
}

This will result in a compiler error:

Cannot implicitly convert type 'EngineFactory' to 'IFactory'. An explicit conversion exists (are you missing a cast?)

on this line: _factories[typeof (Engine)] = new EngineFactory();

Ok that makes sense to me, without specifying the variance this will never work. So I tried to add the out keyword to the generic type T, but that will force me to remove the T as method parameter (because it's not allowed to use <out T> as input parameter):

public interface IFactory<out T> where T : IPart
{
    void Produce(IPart part);
}

This clearly breaks my generic design. I am able to produce Wheels at my EngineFactory.

I understand the challenge lies in these two requirements:

  • Store the factory as IFactory<IPart>
  • The need for a generic void Produce(T part); implementation in the factories.

Is there any way of achieving this?


Solution

  • You can mark generic type parameter as covariant if it used only as return type of methods. Change Produce method to return part and all will work

    public interface IFactory<out T> where T : IPart
    {
        T Produce();
    }
    

    You can't use covariant type parameter if it is used as method parameter.

    BTW it's really strange factory which accepts objects instead of creating them.


    UPDATE you can use runtime type definition with dynamic:

    public class Company
    {
        private Dictionary<Type, dynamic> _factories;
    
        public Company()
        {
            _factories = new Dictionary<Type, dynamic>();
            _factories[typeof(Engine)] = new EngineFactory();
        }
    
        public void SendOrderIntakeToFactory(IPart part)
        {
            _factories[part.GetType()].Produce((dynamic)part);
        }
    }
    

    When you will call

    company.SendOrderIntakeToFactory(new Engine());
    

    Then EngineFactory will be selected and it's Produce method will be called with parameter of runtime type Engine.