Roadmap › C# Language — Basics to Advanced › Module: Methods and Functional C#
Lessons in this course (38)
25. Covariance and contravariance
AdvancedMediumLesson 25 of 38
Variance answers: "if Dog is an Animal, can a Something<Dog> be used where a Something<Animal> is expected?"
| Term | Direction | Keyword | Works when T is… | Example |
|---|---|---|---|---|
| Covariance | Dog → Animal (more derived to less) | out T | only returned (output) | IEnumerable<out T>, Func<out TResult>, IReadOnlyList<out T> |
| Contravariance | Animal → Dog (less derived to more) | in T | only accepted (input) | Action<in T>, IComparer<in T>, Predicate<in T> |
| Invariance | Exact type only | — | both in and out | List<T>, IList<T>, Dictionary<K,V> |
csharp
class Animal { public string Name = ""; }
class Dog : Animal { }
IEnumerable<Dog> dogs = new List<Dog> { new() { Name = "Rex" } };
IEnumerable<Animal> animals = dogs; // ✅ covariance: you only read Animals out of it
Action<Animal> feed = a => Console.WriteLine($"Feeding {a.Name}");
Action<Dog> feedDog = feed; // ✅ contravariance: something that handles any Animal can handle a Dog
Func<Dog> makeDog = () => new Dog();
Func<Animal> makeAnimal = makeDog; // ✅ covariant return
// List<Animal> list = new List<Dog>(); // ❌ invariant: otherwise you could Add(new Cat()) into a list of dogs
// Your own variant interface
public interface IProducer<out T> { T Produce(); } // T only in output positions
public interface IConsumer<in T> { void Consume(T item); } // T only in input positionsArrays are covariant too (object[] objs = new string[3];), but it's unsafe: objs[0] = 1; compiles and throws ArrayTypeMismatchException at runtime — one reason to prefer generic collections.
Interview tip: Remember "out = things come out (return values) → covariance; in = things go in (parameters) → contravariance". Variance works only for interfaces and delegates, and only with reference types (
IEnumerable<int>is not anIEnumerable<object>).