1

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, EventArgs 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?

Community
  • 1
  • 1
DougJones
  • 774
  • 1
  • 9
  • 26

3 Answers3

6

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?

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • I suspect I'm not reading this correctly. You state "There is no mapping from types to types that preserves the direction of assignment compatibility." What about `T -> IEnumerable`? Then if a `T` is assignable to a variable of type `U`, it is also the case that an `IEnumerable` is assignable to a variable of type `IEnumerable`. Conversely if instances of `T` is not assignable to variables of type `U`, the same will be true of instances of `IEnumerable` for variables of type `IEnumerable`. This seems to be a mapping from types to types that preserves assignment compatibility. – jason Feb 02 '11 at 21:41
  • @Jason: I meant "there is no such mapping used in your program". I've updated the text. – Eric Lippert Feb 02 '11 at 22:24
  • +1 Thanks! That clears up covariance for me. I just need to spend some more time working on my contravariance understanding. – DougJones Feb 04 '11 at 13:23
5

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.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thanks for clearing that up. So let's say B has string Title in it. I'd like to assign all members from A to B, then assign Title separately. Besides explicitly assigning all properties individually, is there a shortcut? That's all I'm actually after. – DougJones Feb 02 '11 at 16:05
  • @Doug - AutoMapper maybe ? It also isn't unheard of to use a serialization round-trip to change between alike models, but AutoMapper is probably your easiest option here. – Marc Gravell Feb 02 '11 at 16:13
  • Great...hadn't heard of the AutoMapper project. That helps my efficiency but I was thinking the implicit cast would work and keep the runtime efficient. Anyway, now I know...thanks again. – DougJones Feb 02 '11 at 16:20
1

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);
Dan Bryant
  • 27,329
  • 4
  • 56
  • 102