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?"

TermDirectionKeywordWorks when T is…Example
CovarianceDog → Animal (more derived to less)out Tonly returned (output)IEnumerable<out T>, Func<out TResult>, IReadOnlyList<out T>
ContravarianceAnimal → Dog (less derived to more)in Tonly accepted (input)Action<in T>, IComparer<in T>, Predicate<in T>
InvarianceExact type only—both in and outList<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 positions

Arrays 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 an IEnumerable<object>).