Summary: in this tutorial, you’ll learn about C# covariance that allows you to write more flexible generic code.
Introduction to C# Covariance
Inheritance allows you to define an is-a relationship between classes. For example, an employee is a person. Therefore, you can define an Employee
class that inherits from a Person
class like this:
class Person { public string Name { get; set;} public Person(string name) { Name = name; } } class Employee : Person { public string JobTitle { get; set;} public Employee(string name, string jobTitle) : base(name) { JobTitle = jobTitle; } }
Code language: C# (cs)
In C#, covariance means the order is the same as an inheritance. For example, a list of employees is a list of people. So a list is covariant.
By definition, covariance allows a derived class or interface to return a more derived type that is returned by the base class or interface method. C# supports the covariance in generic interfaces and delegates.
To define a covariance, you use the out
keyword. For example:
interface MyInterface<out T> { }
Code language: PHP (php)
The keyword out
means that you can only return T
from the methods of the interface. In other words, you cannot use T
as the parameter of any method. For example:
interface MyInterface<out T> { T GetNext(); }
Code language: PHP (php)
But the following example will result in an error because we use T
as the parameter of the Add()
method:
interface MyInterface<out T> { T GetNext(); // Invalid variance: The type parameter 'T' must be contravariantly // valid on 'MyInterface<T>.Add(T)'.'T' is covariant void Add(T item); // ERROR }
Code language: PHP (php)
.NET has many built-in generic interfaces that are covariant, for example, the IEnumerable<T>
interface:
public interface IEnumerable<out T> : System.Collections.IEnumerable
Code language: C# (cs)
Because IEnumerable<T>
is a covariance, you can assign a list of Employee
objects to a list of Person
objects like this:
IEnumerable<Employee> employees = new List<Employee>() { new Employee("John Doe","C# Developer"), new Employee("Jane Doe","UI/UX Designer") }; IEnumerable<Person> people = employees;
Code language: C# (cs)
This example shows that the Employee
object is upcasted to the Person
object. If the IEnumerable<T>
is not a covariant, the code will not compile.
Also, you can pass a list of Employee
object to a method that accepts a list of Person
object like the following example:
using static System.Console; class Person { public string Name { get; set; } public Person(string name) { Name = name; } } class Employee : Person { public string JobTitle { get; set; } public Employee(string name, string jobTitle) : base(name) { JobTitle = jobTitle; } } class Program { public static void Display(IEnumerable<Person> people) { foreach (var person in people) { WriteLine(person.Name); } } public static void Main(string[] args) { IEnumerable<Employee> employees = new List<Employee>() { new Employee("John Doe","C# Developer"), new Employee("Jane Doe","UI/UX Designer") }; Display(employees); } }
Code language: C# (cs)
Output:
John Doe Jane Doe
Code language: C# (cs)
Defining a covariant interface in C#
The following example defines the IGroup<T>
interface and Group<T>
class that implements the IGroup<T>
interface.
using static System.Console; class Person { public string Name { get; set; } public Person(string name) { Name = name; } } class Employee : Person { public string JobTitle { get; set; } public Employee(string name, string jobTitle) : base(name) { JobTitle = jobTitle; } } interface IGroup<out T> { IEnumerable<T> GetAll(); } class Group<T> : IGroup<T> { private readonly List<T> list = new(); public Group(List<T> list) { this.list = list; } public IEnumerable<T> GetAll() => list; } class Program { public static void Display(IGroup<Person> people) { foreach (var person in people.GetAll()) { WriteLine(person.Name); } } public static void Main(string[] args) { var employees = new List<Employee>() { new Employee("John Doe","C# Developer"), new Employee("Jane Doe","UI/UX Developer") }; IGroup<Employee> employeeGroup = new Group<Employee>(employees); Display(employeeGroup); } }
Code language: C# (cs)
If you remove the out keyword from the IGroup<out T> interface, the program will not compile and return the following error:
Cannot implicitly convert type 'IGroup<Employee>' to 'IGroup<Person>'. An explicit conversion exists (are you missing a cast?)
Code language: C# (cs)
Summary
- Covariance means that when you have a hierarchy of classes or interfaces, a method in a derived class or interface can return a more specific type than the same method in its base class or interface.
- Covariance allows you to write more flexible and adaptable code.
- Use the out keyword to define a covariance.