Article Categories
- All Categories
-
Data Structure
-
Networking
-
RDBMS
-
Operating System
-
Java
-
MS Excel
-
iOS
-
HTML
-
CSS
-
Android
-
Python
-
C Programming
-
C++
-
C#
-
MongoDB
-
MySQL
-
Javascript
-
PHP
-
Economics & Finance
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 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.
