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?
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?
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.
精彩评论