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.