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 ?