What is the difference between covariance and contravariance in C# generics?
Understand covariance and contravariance in C# generics: what out and in mean, how variance keeps generic assignments type-safe, with clear examples.
Expected Interview Answer
Covariance lets you use a more derived type where a less derived one is expected (output positions, marked with 'out'), while contravariance lets you use a less derived type where a more derived one is expected (input positions, marked with 'in'). Together they make generic assignment compatibility follow the direction of the type hierarchy safely.
In C#, variance applies only to generic interfaces and delegates, not to classes. A covariant type parameter (out T) can appear only in output positions such as return values, so IEnumerable<string> is assignable to IEnumerable<object>. A contravariant type parameter (in T) can appear only in input positions such as method arguments, so Action<object> is assignable to Action<string>. The compiler enforces these position rules to keep the assignments type-safe at runtime, and value types never participate because variance relies on reference conversions.
- Enables safe, intuitive assignment along the inheritance chain
- Lets IEnumerable<T> and IComparer<T> compose flexibly
- Removes needless casting and wrapper code
- Keeps type safety verified at compile time
- Improves reusability of generic APIs
AI Mentor Explanation
Covariance is like a squad list that promises only to hand you players: a list of fast bowlers can safely stand in for a list of bowlers because everything it gives out is still a bowler. Contravariance is like a coach who accepts any batter: a coach willing to train any player can obviously train an opener, because he only takes players in, never hands a specific type back.
Step-by-Step Explanation
Step 1
Recognise variance is interface/delegate only
Only generic interfaces and delegates can be variant in C#; concrete generic classes like List<T> are invariant.
Step 2
Mark output with out
Use out T for covariance when the parameter appears only in return/output positions, e.g. IEnumerable<out T>.
Step 3
Mark input with in
Use in T for contravariance when the parameter appears only in argument/input positions, e.g. IComparer<in T> or Action<in T>.
Step 4
Let the compiler verify positions
The compiler rejects a covariant parameter used as input or a contravariant one used as output, preserving safety.
Step 5
Remember reference types only
Variant conversions rely on reference conversions, so value types such as int never participate in variance.
What Interviewer Expects
- Correct mapping of out to covariance and in to contravariance
- Knowledge that variance is limited to interfaces and delegates
- Concrete examples like IEnumerable<T> and Action<T>
- Awareness of output vs input position rules
- Understanding that value types are excluded
Common Mistakes
- Claiming classes like List<T> support variance
- Swapping the meanings of in and out
- Thinking value types participate in variant conversions
- Believing arrays' unsafe covariance is the same as generic variance
- Using a covariant parameter in an input position
Best Answer (HR Friendly)
“Covariance and contravariance are rules that decide when you can use one generic type in place of another based on inheritance. Covariance flows with the hierarchy for things that only output values, and contravariance flows against it for things that only accept values, and C# keeps both safe automatically.”
Code Example
// Covariance: out T (used in output positions)
IEnumerable<string> strings = new List<string> { "a", "b" };
IEnumerable<object> objects = strings; // string is more derived than object
// Contravariance: in T (used in input positions)
Action<object> printObject = o => Console.WriteLine(o);
Action<string> printString = printObject; // object is less derived than string
printString("hello");
// Custom variant interfaces
interface IProducer<out T> { T Produce(); } // covariant
interface IConsumer<in T> { void Consume(T item); } // contravariantFollow-up Questions
- Why does C# forbid variance on generic classes?
- Why can't value types participate in variance?
- How is array covariance different and why is it unsafe?
- Where does IComparer<in T> use contravariance?
- What happens if you put an out parameter in an input position?
MCQ Practice
1. Which keyword marks a covariant type parameter in C#?
out marks covariance, allowing the type parameter only in output positions such as return values.
2. Which assignment is valid due to contravariance?
Action<in T> is contravariant, so an Action<object> can be assigned to an Action<string> because object is less derived.
3. Variance in C# is supported on which constructs?
Only generic interfaces and delegates can declare variant type parameters; classes and structs are invariant.
Flash Cards
What does 'out T' mean? — Covariance: T may appear only in output positions, enabling assignment to a less derived generic type.
What does 'in T' mean? — Contravariance: T may appear only in input positions, enabling assignment from a less derived generic type.
Where is variance allowed? — Only on generic interfaces and delegates, never on concrete generic classes.
Do value types support variance? — No — variance depends on reference conversions, so types like int are excluded.