Covariance and Contravariance
Which generic directions convert safely, why Dictionary is invariant, and the IEnumerable<out T> rule.
Two directions of safe conversion
Covariance (out T): if string : object, then IEnumerable<string>
converts to IEnumerable<object>. Output positions only โ you can pull a
string out and hand it over as object.
IEnumerable<object> shapes = new List<string> { "a" }; // legal: out T
Contravariance (in T): Action<object> converts to Action<string>.
Input positions only โ a delegate that accepts any object can serve where a
string-eater is needed.
Action<object> logAny = o => Console.WriteLine(o);
Action<string> logStr = logAny; // legal: in T
Both are compile-time guarantees the compiler checks for you: a T in an
output position makes the parameter covariant-eligible; in input positions,
contravariant. Mutating interfaces (IList<T>, Dictionary<K,V>) are
invariant โ a list you can insert into must reject the conversion, or
shapes.Add(42) would corrupt a List<string>.
The mental model
out T= "I only produce T" โ covariant (more specific collection is-a less specific one).in T= "I only consume T" โ contravariant (a general consumer is-a specific one).- Both = invariant (I do both; no safe direction).
Func<in T, out TResult> has both annotations โ read it as a promise about
each position.
Check your understanding
- Why is
IList<string>โIList<object>illegal? (Add(object) would insert non-strings.) Comparison<in T>โ which variance and why? (Contravariant: it consumes T.)