开发者

Covariance/Contravariance Conundrum when using generic interface constraints

开发者 https://www.devze.com 2023-03-08 18:44 出处:网络
public interface IShape{} public class Rectangle : IShape{} public class Base{} public class Derived : Base{}
    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.

0

精彩评论

暂无评论...
验证码 换一张
取 消