๐ WAYPOINT LESSON
Variance: co, contra, in
โญโญโญ advancedโณ 14 min read๐ Lesson 167 of 265
Why IEnumerable<T> is out, Action<in T> is in, IList<T> is neither โ and what that buys you.
Variance: co, contra, in
Variance answers one question: when is C<Derived> usable as C<Base>?
The direction follows data flow, and the compiler enforces safety:
- Covariant (
out T): T only exits.IEnumerable<out T>,IReadOnlyList<out T>. A sequence of cats is a sequence of animals. - Contravariant (
in T): T only enters.Action<in T>,IComparer<in T>,Predicate<in T>. A consumer of animals can consume cats. - Invariant (plain
T): T both enters and exits.IList<T>,Dictionary<TKey,TValue>(keys enter, values exit). Variance would be unsound the moment a value could flow both ways through the same slot.
IEnumerable<object> animals = new List<Cat>(); // OK: out
Action<Cat> feedCat = c => c.Eat();
Action<Animal> feedAny = a => a.Eat();
feedCat = feedAny; // OK: in โ an Animal-consumer handles Cats
// feedAny = feedCat; // compile error โ would feed a Dog to Cat code
Two habits worth keeping forever:
- Design your own interfaces with variance in mind. A read-only
repository
IRead<out T>composes with base-type handlers; an invariant one forces casts at every boundary. - Variance is interface/delegate only. Classes are invariant โ wrap a covariant view over internal storage instead of fighting it.
The trap that survives review: IEnumerable<object> xs = strings is fine,
but xs.Add-style mutation doesn't exist there โ the moment you need
write access you need an invariant type, and pretending otherwise is how
InvalidCastException at a distance happens (arrays are the legacy
exception โ covariant but checked at write time, at runtime cost).
โก Now practice
Ready to CodeGeneric machineryVariance drills, static-abstract abstractions, and allocation reasoning for generic code.
3 challenges ยท ยท ~45 min