Covariant and contravariant

Convention:

  • A ≦ BThat means A is A subtype of B
  • A and BRefers to A function type that takes A as its argument type and B as its return value type
  • x : AThat means x is of type A

The concepts of covariant and contravariant can be understood with the aid of actual variable types:

  • Covariant and ordinary variables:

In C#, the List class implements IEnumerable, so List implements IEnumerable:

  IEnumerable<Derived> d = new List<Derived>();
  IEnumerable<Base> b = d;
Copy the code

Simply put, covariant, like polymorphism in object-oriented concepts, refers to a situation in which a parent type reference can be used to point to an instance of a child type.

  • Contravariant and function variables:

If I have A type chain C ≦ B ≦ A, and A function f(B → B) takes A function B → B as an argument, what function can be used as an argument to f?

First, this is not possible for C → *, because f may call a function with arguments of type B or other subtypes of B, while C → * only supports input arguments of type C.

Then, it is also not possible for * → A, since f requires that the return value be B or some other subtype of B, but * → A may return B’s parent type A.

Finally, it is feasible for A → C, because f’s argument will only be B or some other subtype of B, and A → C’s input type will be A, satisfying. Meanwhile, the return value of function A → C is C, which is A subtype of B, and the return value type also satisfies.

Here, the magic happens. When the function type is B → B, we can use

to assign:

  Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
  Action<Derived> d = b;
Copy the code

This is contravariant.

Reference:

  • Covariant and contravariant – Wikipedia, the free encyclopedia
  • Generic covariant and inverter in | Microsoft Docs
  • Covariance and inverter | deep understanding of the TypeScript