Covariance and Contravariance in C#

Covariance and contravariance in C# enable flexible type relationships when working with generics, delegates, and interfaces. Covariance allows you to use a more derived type than originally specified, while contravariance allows you to use a more general type than originally specified.

These concepts are essential for understanding how type safety works with generic interfaces and delegates, particularly when dealing with inheritance hierarchies.

Class Hierarchy Example

Let us consider the following class hierarchy where One is the base class, Two inherits from One, and Three inherits from Two

using System;

class One { 
   public virtual void Display() {
      Console.WriteLine("One");
   }
}

class Two : One {
   public override void Display() {
      Console.WriteLine("Two");
   }
}

class Three : Two { 
   public override void Display() {
      Console.WriteLine("Three");
   }
}

class Program {
   public static void Main() {
      One obj = new Two(); // Base can hold derived
      obj.Display();
      
      One obj2 = new Three(); // Base can hold derived
      obj2.Display();
   }
}

The output of the above code is −

Two
Three

Covariance vs Contravariance Covariance (out) More Derived ? Less Derived IEnumerable<Dog> can be assigned to IEnumerable<Animal> Used in return types Contravariance (in) Less Derived ? More Derived Action<Animal> can be assigned to Action<Dog> Used in input parameters

Covariance with Interfaces

Covariance allows you to use a more derived type where a base type is expected. This is enabled using the out keyword in generic interfaces −

using System;
using System.Collections.Generic;

interface ICovariant<out T> {
   T GetItem();
}

class CovariantClass<T> : ICovariant<T> {
   private T item;
   public CovariantClass(T item) {
      this.item = item;
   }
   public T GetItem() {
      return item;
   }
}

class Program {
   public static void Main() {
      // Covariance: ICovariant<Two> can be assigned to ICovariant<One>
      ICovariant<Two> twoVariant = new CovariantClass<Two>(new Two());
      ICovariant<One> oneVariant = twoVariant; // Covariance in action
      
      oneVariant.GetItem().Display();
      
      // Works with built-in interfaces like IEnumerable
      IEnumerable<Two> twoList = new List<Two> { new Two(), new Two() };
      IEnumerable<One> oneList = twoList; // Covariance
      
      Console.WriteLine("Count: " + ((List<Two>)twoList).Count);
   }
}

class One { 
   public virtual void Display() {
      Console.WriteLine("One");
   }
}

class Two : One {
   public override void Display() {
      Console.WriteLine("Two");
   }
}

The output of the above code is −

Two
Count: 2

Contravariance with Delegates

Contravariance allows you to use a more general type where a specific type is expected. This is enabled using the in keyword −

using System;

delegate void ContravariantDelegate<in T>(T item);

class Program {
   public static void ProcessOne(One item) {
      Console.WriteLine("Processing One: ");
      item.Display();
   }
   
   public static void ProcessTwo(Two item) {
      Console.WriteLine("Processing Two: ");
      item.Display();
   }

   public static void Main() {
      // Contravariance: delegate expecting One can handle Two
      ContravariantDelegate<One> oneDelegate = ProcessOne;
      ContravariantDelegate<Two> twoDelegate = oneDelegate; // Contravariance
      
      twoDelegate(new Two());
      
      // Another example with Action<T>
      Action<One> actionOne = ProcessOne;
      Action<Two> actionTwo = actionOne; // Built-in contravariance
      
      actionTwo(new Two());
   }
}

class One { 
   public virtual void Display() {
      Console.WriteLine("One");
   }
}

class Two : One {
   public override void Display() {
      Console.WriteLine("Two");
   }
}

The output of the above code is −

Processing One: 
Two
Processing One: 
Two

Syntax

Following is the syntax for declaring covariant and contravariant generic interfaces −

// Covariant interface - out keyword
interface ICovariant<out T> {
   T GetValue(); // T can only be used as return type
}

// Contravariant interface - in keyword  
interface IContravariant<in T> {
   void SetValue(T value); // T can only be used as parameter type
}

Comparison

Feature Covariance (out) Contravariance (in)
Direction Derived ? Base Base ? Derived
Usage Return types, output Input parameters
Example IEnumerable<T> Action<T>
Type Safety Safe to read Safe to write

Conclusion

Covariance and contravariance in C# provide type flexibility while maintaining safety. Covariance with out allows derived types to be used where base types are expected in return positions, while contravariance with in allows base types to be used where derived types are expected in parameter positions.

Updated on: 2026-03-17T07:04:35+05:30

368 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements