Covariance and Contravariance in Generics C# With Example

Covariance and Contravariance were introduced in C# 4.0, offering implicit reference conversion for array types, delegate types, and generic type arguments. These concepts preserve and reverse assignment compatibility, respectively.

Table of Contents

  1. Covariance
  2. Contravariance
  3. Real World Example - Covariance
  4. Real World Example - Contravariance
  5. Covariance and Contravariance in .net framework
ObjectPerson+string FirstName+string LastNameTeacher+Teacher(string FirstName, string LastName)Student+string Grade+Student(string FirstName, string LastName, string grade)

To understand Covariance and Contravariance, we must first understand two terms: Less Derived and More Derived. As shown in the above class diagram, there are three classes: Person, Student, and Teacher (object is not shown in the image).

  • The object is the ancestor of all types; it is always the Less Derived type than other types. Person type is More Derived according to the Object type.
  • The Student and Teacher type is More Derived according to Object and Person types…

Covariance

Covariance occurs when more derived types are assigned to less derived types. In C#, this is applicable to generic types with the out parameter.

IEnumerable<Student> students = new List<Student>();
IEnumerable<Person> teachers = students;

Contravariance

Contravariance occurs when a less derived type is assigned to a more derived type. In C#, this is applicable to generic types with the in parameter.

Action<Person> printToConsole = (target) =>
{
    Console.WriteLine($"{target.FirstNmae},{target.LastName}");
};
Action<Student> print = printToConsole;
print(new Student("John", "Doe", "IV"));

Real World Example - Covariance

Consider a scenario where you want to print information about a collection of people. Covariance allows you to pass a collection of more derived types (Student) to a method expecting less derived types (Person).

public static void Print(IEnumerable<Person> people){
    
    foreach (var person in people)
    {
        Console.WriteLine($"{person.FirstNmae}-{person.LastName}");
    }
}

static void Main()
{
    IEnumerable<Student> students = new List<Student>(){
        new Student("John","Doe","11"),
        new Student("Julie","Doe","12"),
        new Student("Bill","Gates","10"),
        new Student("Aron","Sane","13")
    };
    Print(students);
}

Real World Example - Contravariance

Consider a scenario where you want to compare people based on their first names. Contravariance allows you to pass a comparer for less derived types (Person) to a method expecting a comparer for more derived types (Student).

static void Main()
{
    IComparer<Person> personComparer = new PersonComparer();
    IComparer<Student> studentComparer = personComparer;
    var students = new List<Student>(){
        new Student("John","Doe","11"),
        new Student("Julie","Doe","12"),
        new Student("Bill","Gates","10"),
        new Student("Aron","Sane","13")
    };
    students.Sort(studentComparer);
    Console.WriteLine(students);
}

Covariance and Contravariance in .net framework

In C#, the in and out variance annotations are applied to generic interfaces and delegates to indicate the direction in which type parameters are allowed to vary. These annotations are used with generic types to define their behavior when dealing with inheritance.

  • out (Covariant): Indicates that a type parameter is covariant, meaning it can only appear in output positions (e.g., return types).

  • in (Contravariant): Indicates that a type parameter is contravariant, meaning it can only appear in input positions (e.g., method parameters).

When the same type is assigned to the same type, it is called Invariance.

Please do not post any spam link in the comment box😊

Post a Comment (0)
Previous Post Next Post