MattC
MattC

Reputation: 4004

Covariance/Contravariance Conundrum when using generic interface constraints

    public interface IShape{}

    public class Rectangle : IShape{}

    public class Base{}

    public class Derived : Base{}

    public interface IFoo<out T, in U>
        where T : IShape
        where U : Base
    {
        T Convert(U myType);
    }

    public class MyFoo : IFoo<Rectangle, Derived>
    {
        public Rectangle Convert(Derived myType)
        {
            throw new NotImplementedException();
        }
    }    

    class Program
    {
        static void Main(string[] args)
        {
            IFoo<IShape, Base> hmm = new MyFoo();
        }
    }

Given the above code, the compiler is unable to determine how to assign the type MyFoo to IFoo<IShape, Base>, presumably because U is set as an out meaning that it can accept less derived. However, Derived is, well, more derived than Base, so generates a compiler error.

This example is contrived but the implementation we are dealing with is one in which MyFoo would be returned from a factory.

Although U is used as a parameter, it is also an output when trying to assign it to the generic interface but I am unable to use the out keyword here. How could we work around this?

Upvotes: 3

Views: 774

Answers (2)

James Michael Hare
James Michael Hare

Reputation: 38397

Your IFoo interface seems to be wrong in this usage, it should be:

public interface IFoo<out T, **out** U>

With U being out. Remember that an out generic type parameter means that it can vary "outwards". That is, you can widen the type implicitly to a wider type. In, though, means that you can implicitly narrow the type "inwards" to a more specific type. These are just rough analogies, of course.

So in the case of the assignment of hmm, you are are implicitly trying to widen the interface generic type parameter for U from Derived to Base, but the interface declares it to be narrowing (in):

IFoo<IShape, Base> hmm = new MyFoo();

So it can't make the implicit conversion. If you really want to be able to widen this interface implicitly, the second type argument should be out instead of in.

Update: after your comments, I see that the big dilemma is that you want it to be both in and out, which isn't really possible Since it's a contravariant input, you can't assign the interface covariantly to IFoo<IShape, Base>, unfortunately.

You either need to code around the fact you can't assign to IFoo<IShape,Base> or what you could do is create Foo as:

public class MyFoo : IFoo<Rectangle, Base>

And then cast to Rectangle inside the implementation. The main thing is you can't have both covariance and contravariance on the same type parameter.

Does this make sense?

Upvotes: 1

supercat
supercat

Reputation: 81149

Something that can convert a Base into a Rectangle will also turn a Derived into an IShape. Something that can convert a Derived into a Rectangle, however, may not be able to do anything useful with a Base. You correctly identified that the covariance specifier for your second parameter needs to be "in", but are then trying to use the covariance in the way opposite what it actually supports.

Upvotes: 0

Related Questions