Covariance and Contravariance

OK, so this is my attempt to help me to commit something to memory. I am not an expert in this area by any means.

Variance which is, loosely, the subtyping relationship for objects and substitutions that may be permitted for those types. Generally, more derived types can be assigned to objects of less derived types e.g Cat objects can be used in places where we call for an Animal. This particular type of variance is called co-variance, but there is also Contravariance and Invariance in C# types.

Generally speaking….

Covariance allows you to return a more derived class from a method. True contravariance would allow you to change the method signature so you would have in the Animal class, a method Animal GetAnimal() and in the Cat class, it’s override as Cat GetAnimal().

Contravariance would allow a less derived class to be used in method arguments so if the Animal class had a method called BuyAnimal(Animal animal), the Cat class could override this with BuyAnimal(Object animal).

Neither of these are supported in C#, though Java & C++ have some support.  So what do we need to know about convariance and contravariance in C# or .Net?

Covariance

Some sources refer to it as preserving the standard variance or similar words to that. Basically, it means that if I override a method, the return can be the same as the parent, or a descendant type. This sounds a lot like following the standard inheritance pattern, and while methods in a subclass can’t declare a more derived return type when overriding base class methods, they can of course return a more derived type. Maybe calling this covariance is generous, though it meets some of the definition, as it’s really just polymorphism that is a part of object oriented languages.

More explicit examples of Covariance can be found in generic types, such as IEnumerable<T> and in usage, it looks pretty much the same. The key here is that a generic method can return either T or a subclass of T and can be declared to return different T’s. This works because IEnumerable uses the ‘out’ variance modifier.

//Covariance - all bout how the return type can be a child of a parent
Animal spot = new Dog(Gender.Male);
Animal[] mammals = new Mammal[10];
IEnumerable<Animal> animals = new List<Dog>();

You can also see covariance in IEnumerator<T>, IQueryable<T> and IGrouping<TKey, TElement>.

Contravariance

Contravariance is reversing this – contrary to the normal variance. In C# Contravariance is limited to delegates and interfaces It means that I am allowed to change the input args to a base class of whatever the args the base class takes. In the case of delegates, I can assign a more general method to more specific delegate.

static void Main(string[] args)
{
  //Contravariance lets a method take parameters that are from a superclass of the type expected by a
  //delegate.
        //Here the delegate takes a Dog
  Dog dog = new Dog(Gender.Female);
  Action<Dog> barkingDog = Bark;
  barkingDog(dog);
}		

//Method here takes a Animal
public static void Bark(Animal animal)
{
  Console.WriteLine($"Woof, im a {nameof(animal)}");
}

You may look at the code above and say, what’s the point of that? While it’s true that it doesn’t buy us much in this case, consider scenarios where those delegates are representing methods with a less direct relationship. In this case it starts to become more useful. For example, if the Dog class has a property called Action<Dog TalkDelegate. You can pass the Bark(Animal animal) method to that property, even though the method signature doesn’t match what the property requires. This is because as Dog is a Subclass of animal, a method working with an animal will also be able to work with a Dog.

You also see Contravaraince in IComparer<T>, IEqualityComparer<T> and ICompatible<T>

Invariant classes

Some types and interfaces are invariant. For example, while IEnumberable<T> is covariant. ILIst<T> is invariant. That means that the code below will not compile.

//Invariance
IList<Animal> animals = new List<Dog>();

Variance Modifiers

There are two variance modifiers that can be applied: ‘in’ and ‘out’. Only delegates and Interfaces can take Variance modifiers. The reason IEnumerable supports Covariance is that it’s declared as IEnumerable<out T>, and delegates by default are constructed using ‘in’, such as Action<in T>. These keywords can be seen a linking the variance of the output or the inputs.

  • out T – Covariance lets a method return a value from a subclass of the result expected by a delegate
  • in T – Contravariance lets a method take parameters that are from a superclass of the type expected by a delegate.

Variance is supported by reference types only and just because a class implements one of the interfaces listed here, it doesn’t mean those types support it. For example, List<T> implements IEnumerable but is invariant.

Hope you found this helpful.

Sources/References: