C# covariance and contravariance
last modified October 5, 2026
Covariance and contravariance describe safe conversions between
constructed generic types. In C#, generic interfaces and delegates can
declare variance: out marks a covariant type parameter, and
in marks a contravariant one.
Suppose Dog derives from Animal. A
conversion from a dog-producing type to an animal-producing type is safe,
while a consumer of any animal can also consume a dog. Variance allows
these conversions for compatible generic interfaces and delegates.
C# covariance example
A covariant type parameter appears only in output positions. The
built-in IEnumerable<T> is covariant, so a sequence of
dogs can be used where a sequence of animals is expected.
using System.Collections.Generic;
IEnumerable<Dog> dogs = new List<Dog> { new Dog(), new Dog() };
IEnumerable<Animal> animals = dogs;
Console.WriteLine(animals.Count());
class Animal { }
class Dog : Animal { }
$ dotnet run 2
The conversion is safe because each element obtained from the sequence
is an Animal. The out annotation on
IEnumerable<out T> declares this output-only
relationship.
C# contravariance example
A contravariant type parameter is used in input positions. An action that accepts any animal can therefore be assigned to a variable that accepts dogs.
Action<Animal> describe = animal => Console.WriteLine(animal.Name);
Action<Dog> describeDog = describe;
describeDog(new Dog { Name = "Rex" });
class Animal
{
public string Name { get; set; } = "";
}
class Dog : Animal { }
$ dotnet run Rex
Action<in T> is contravariant. A delegate that can
handle every animal can handle a dog, so the assignment is safe.
Declaring variance
Custom interfaces and delegates can declare variance for
reference-type conversions. This example declares a producer with an
out parameter and a consumer with an in
parameter.
interface IProducer<out T>
{
T Get();
}
interface IConsumer<in T>
{
void Put(T value);
}
A covariant type parameter must be used in output positions, while a contravariant parameter must be used in input positions. A type parameter without either annotation is invariant.
Variance limitations
- Variance annotations are supported on generic interfaces and delegates, not generic classes or structs.
- Variance conversions apply to reference types; they do not make
IEnumerable<int>convertible toIEnumerable<object>. - Type parameters of generic methods and types remain subject to the input/output position rules.
- Variance changes assignment compatibility; it does not change the underlying object or copy its data.
Source
Covariance and contravariance - C# programming guide
This article introduced covariance and contravariance in C# generics.
Author
List all C# tutorials.