开发者

Implicit contravariance on classes - simple failed example

开发者 https://www.devze.com 2023-02-08 17:18 出处:网络
There are some great resources on covariance and contravariance here on StackOverflow, but I seem to misunderstand the fundamentals of contravariance.I expect this example to work:

There are some great resources on covariance and contravariance here on StackOverflow, but I seem to misunderstand the fundamentals of contravariance. I expect this example to work:

    public partial class WebForm1 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArg开发者_JAVA百科s e)
    {
        A a = new B();
        B b = new A();
    }
}

public class A
{
    int id { get; set; }
}
public class B : A
{
}

Setting a to B works, which is covariance, but setting b to a new A fails with a compile error. Even doing an explicit cast still generates error at compile time. Is there a way to do this or do I just completely misunderstand contravariance?


do I just completely misunderstand contravariance?

Yes. You have completely and totally misunderstood what "covariance" and "contravariance" mean. You have confused them with assignment compatibility.

This is an extremely common error. The two concepts are related, but they are not at all the same.

Assignment compatibility is the property that an expression of one type may be stored in a variable of another type.

Covariance is the property that a mapping from types to types preserves the direction assignment compatibility. If Giraffe is assignment compatible with Animal, and this implies that IEnumerable<Giraffe> is assignment compatible with IEnumerable<Animal> then the IEnumerable<T> mapping is covariant.

See my article on the subject for more details:

http://blogs.msdn.com/b/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx

Setting a to B works, which is covariance

Yes it works. "a" is assignment compatible with B. It is not "covariance" because nothing is varying. There is no mapping from types to types that preserves the direction of assignment compatibility used in the assignment "a = B"; nothing is even generic.

but setting b to a new A fails with a compile error.

Correct.

Is there a way to do this ?

No. Instead of "A" and "B", call them Animal and Giraffe. Every Giraffe is an Animal, so if a variable can hold an Animal, then it can hold a Giraffe. If you try to go the other way, you can't put an Animal into a variable of type Giraffe. The Animal might actually be a Tiger. Why should you be allowed to put a Tiger into a variable of type Giraffe?


You can't do:

B b = new A();

Because an A simply isn't a B. The assignment is invalid. I'm not sure I'd even call this variance - it is simply inheritance.

In the general case where B had members that A doesn't, you can see that it makes no sense to do (if b actually holds a reference to an A object):

b.SomeMethod();

where SomeMethod is defined only in B, but this logic extends to the assignment to the variable itself. Even if you added a cast, the cast has an implicit type check that would fail.


Contravariance is not about violating casting rules; it applies in a situation like:

void WithApple(Action<Apple> eat);

Now, suppose you have some method that knows how to eat any kind of fruit:

void EatFruit(Fruit fruit);

Since it can eat any kind of fruit, you should be able to say:

WithApple(EatFruit);

This was not possible before contravariance support, as the delegate had to match exactly.

This is distinct from covariance, which is the slightly more intuitive notion of:

void EatAllFruit(IEnumerable<Fruit> inBasket);

where you should be able to:

IEnumerable<Apple> apples = someBasket;
EatAllFruit(apples);
0

精彩评论

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