C# Covariance

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.IEnumerableCode 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 DoeCode 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.
Was this tutorial helpful ?