What is covariance and contravariance in C#?

C# Type System Advanced 3–5 Yrs5+ Yrs Conceptual 1 min read

Quick Answer

Covariance (out) lets you use a more-derived type than specified — IEnumerable<string> is assignable to IEnumerable<object> because T only comes out. Contravariance (in) lets you use a less-derived type — an Action<object> is assignable to Action<string> because T only goes in.

By default generics are invariant — a List<string> is not a List<object>, and that's on purpose, otherwise you could add an int to your string list through the object reference. Covariance, the out keyword, allows it when the type parameter only comes out of the interface — that's why IEnumerable<string> can be treated as IEnumerable<object>, you're only ever reading. Contravariance, in, is the opposite — when T only goes in, like a comparer or an Action, a handler for the base type can stand in for a handler of the derived type. It only works on interfaces and delegates.

Generic type parameters are invariant by default: List<string> is not a List<object> (if it were, you could add an int to it). Variance is only safe for interfaces and delegates where T is used in one direction:

  • out T (covariant) — T appears only in output positions (return values). IEnumerable<out T>, IReadOnlyList<out T>, Func<out TResult>, Task<T> conceptually.
  • in T (contravariant) — T appears only in input positions (parameters). IComparer<in T>, Action<in T>, IEqualityComparer<in T>.

Arrays are covariant too (string[] → object[]) but unsafely — writing the wrong type throws ArrayTypeMismatchException at run time.