Glossary / Variance: covariance and contravariance

Variance: covariance and contravariance

Variance describes how generic type relationships change when type arguments are related by inheritance. C# uses out for covariance (a producer of T) and in for contravariance (a consumer of T) on interfaces and delegates; classes and structs remain invariant in their generic parameters.

Direction and safety

If Derived inherits from Base, covariance allows IEnumerable<Derived> to be used where IEnumerable<Base> is expected because the interface only produces values. Contravariance allows Action<Base> where Action<Derived> is expected because a function capable of handling any Base can handle a Derived. Invariance is required when a type both consumes and produces T in ways that could violate type safety.

IEnumerable<string> names = new List<string>();
IEnumerable<object> objects = names; // covariance
Action<object> logAny = Console.WriteLine;
Action<string> logText = logAny;     // contravariance

Variance conversions apply only to reference types for variant generic interfaces/delegates, not value-type instantiations. Type parameters declared out cannot appear in input positions; in parameters cannot appear in output positions (subject to language rules). Arrays have historical covariance that can fail at runtime with ArrayTypeMismatchException, so prefer generic collections.

Interview questions

  1. What does out variance mean? A producer can be substituted with a more derived type argument for a base expectation.
  2. What does in mean? A consumer of a base type can substitute where a consumer of a derived type is required.
  3. Are List<Derived> and List<Base> assignable? No, List<T> is invariant.
  4. Does variance work for value types? Variant conversions apply to reference-type arguments, not value-type instantiations.
  5. Why are arrays risky? Their covariance is checked at runtime and writes can fail.

References