Cook Computing


June 2, 2002 Written by Charles Cook

At last, the final installment of my investigation into covariance. The converse of covariant method arguments is contravariant or conformant arguments. In this case the argument of the overidden method in the derived class is less derived than the argument in the parent class, i.e. its derivation goes in a "different" way and hence "contra". Modifying the previous example:

class Child
  // following redefinition of argument is not valid C#
  public override void DoSomething(ArgGrandParent x) { x.A(); }

Obviously this code works:

Child child = new Child();
child.DoSomething(new ArgGrandParent());

and calling DoSomething of class Child virtually via a Parent reference is now also statically correct:

Parent parent = new Child();
parent.DoSomething(new ArgParent());

The virtual call to DoSomething in class Child is passed an instance of ArgParent which is fine because ArgParent supports method A which this version of DoSomething calls.

The slightly confusing aspect of contravariance is at first glance it might seem that the Parent class could get called with an instance of ArgGrandParent but some code shows that this is statically incorrect and will not compile:

Parent parent1 = new Child();
parent1.DoSomething(new ArgGrandParent()); // won't compile
Parent parent2 = new Parent();
parent2.DoSomething(new ArgGrandParent()); // won't compile

DoSomething in class Child can only take an argument of type ArgGrandParent when a reference to an instance of Child is being used. Whenever a reference to an instance of Parent is used, the argument must be of type ArgParent or a class derived from ArgParent.

Unlike covariant arguments, contravariant arguments are statically type-safe, and for this reason the contravariant approach is used in some langauges, for example Sather.