Table of Contents
- Introduction
- Background
- Prerequisites
- Rules for Variance in C# 4.0
- Generic Interface: Covariance and Contravariance
- Real Time Example: Contravariance
- Generic Delegate: Covariance and Contravariance
- References
- Conclusion
- History
This article is all about Contravarance and Covariance in C# .NET 4.0. After going through lots of books and online references, I am finally able to understand what goes in making of covariance and contravariance. Understanding the concept of these two keywords would be as simple as it can be but implementing it in the real world would be a pain. For a technical architect, this concept is a must and one must dive deep to understand it. This article is more towards code driven explanation rather than verbose. My intension is to help one to grasp the true concept and theory behind Covariance and Contravariance.
Most of us must be aware of the concept of 'Equivalence Relations' in Set. This is related to mathematics and one must be wondering why I'm mixing C# with maths here but trust me, we must know this to better understand variance in C#.
In the early college days, we must have came across the mathematics concept of Equivalence. If not, then we've a chance to know this here. A relation R is Equivalence if and only if R is Reflexive, Symmetric and Transitive. If one looks at the definition of Symmetric, it will help you understand Variance in C#.
Symmetric
A relation R is symmetric iff, if x is related by R to y, then y is related by R to x. For example, being a cousin of is a symmetric relation: if X is a cousin of Y, then it is a logical consequence that Y is a cousin of X. Equivalence Relation.
Concept Of Variance
If y is derived from X and y relates to X, then we can assign X to y. Like X=y. This is Covariance.
If Y is derived from X and y relates to X, then we can assign y to X. Like y = X. This is Contravariance.
If class A is related to B, then B is related to A.
Covariance
Class B: A then we can assign A = B.
Contravariance
Class B: A then we can assign B = A.
- Covariance and Contravariance are not supported for generic classes and non generic class.It is applicable only for interface and delegate types.
- C# variance rules are not applicable to value types In other words,
IMath<int>
is not covariance-Contravariance convertible to IMath<double>
, even though int is implicitly convertible to double. - Should follow Inheritance relationship pattern like string is an object. If object is related to string then string is related to object.
Example
Class String:Object
- Covariance =>
Object=string;
- Contravariance=>
string=Object;
Covariance interface and delegate must have 'In'
keyword declaration for input parameter. Contravariance interface and delegate must have 'Out'
keyword declaration for output parameter.
The below code will compile in .NET 4.0 without requiring any explicit conversion or runtime failure.The below conversion is not possible in earlier versions of .NET.
Covariance: In the below interface, we've output parameter in RecievedData()
method and hence we've written OUT
keyword in Interface generics.IMessageRecieved <out T>
.
IMessageRecieved <T > objData = new DataTX <T>();
Once we replace generic template T
with Covariance rule, then the resultant will be:
IMessageRecieved <Object> objData= new DataTX <String>();
interface IMessageRecieved <out T>
{
T RecievedData();
}
We can also have something like this here:
IFactory <Animal> factory = new AnimalFactory <Cat>();
Contravariance: In the below interface, we've input parameter in SendData( T data)
method and hence we've written IN
keyword in Interface generics. IMessageSend <in T>
:
IMessageSend <T > objData = new DataTX <T>();
Once we replace generic template T
with Contravariance rule, then the resultant will be:
IMessageSend <String> objData= new DataTX <Object>();
interface IMessageSend <in T>
{
void SendData(T data);
}
We can also have something like this here:
IFactory <Cat> factory = new AnimalFactory <Animal>();
One must go through the below code for more understanding. One can download the demo code to grasp the understanding of interface variance functionality.
interface IMessageRecieved <out T>
{
T RecievedData();
}
interface IMessageSend <in T>
{
void SendData(T data);
}
class DataTX <T> : IMessageSend <T>, IMessageRecieved<T>
{
private T m_Data;
#region IMessageSend <T> Members ( Implicit Implementation )
void IMessageSend <T>.SendData(T data)
{
this.m_Data = data;
}
#endregion
#region IMessageRecieved <T> Members ( Implicit Implementation )
public T RecievedData()
{
return m_Data;
}
#endregion
}
class Program
{
static void Main(string[] args)
{
IMessageSend <object> objMessageSend = new DataTX <string>();
IMessageSend <string> strMessageSend = new DataTX <object>();
}
}
Interface Icomparer<in T>
supports contravariance and this real time example will help you understand Contravariance. In the below example, we've base comparer class Basecomparer
that has been restricted to Employee
class only as we applied constraint clause of Employee
class to Interface Icomparer
for sorting operation.
Now using Contravariance, we can allow Manager
class to perform sorting operations without its respective BaseComparer
class being defined. We will use basecomparer
of Employee
to sort Manager
list. This is possible because Manager
class has inheritance relationship with Employee class
.
public class Employee
{
private string m_Name;
private int m_ID;
public Employee(string name, int id)
{
m_Name = name;
m_ID = id;
}
public string Name
{
get { return m_Name; }
set { m_Name = value; }
}
public int ID
{
get { return m_ID; }
set { m_ID = value; }
}
}
public class Manager:Employee
{
public Manager(string name, int id) : base(name, id) { }
}
public class BaseComparer <T>: IComparer<T> where T : Employee
{
public int Compare(T x, T y)
{
return (x.ID < y.ID ? 0 : 1);
}
}
class Program
{
static void Main(string[] args)
{
List <Manager > managerList = new List <Manager >();
managerList.Add(new Manager("san", 1));
managerList.Add(new Manager("Sandy", 2));
BaseComparer <Employee > objComparer1 = new BaseComparer <Employee >();
managerList.Sort(objComparer1);
List <Employee > employeeList = new List <Employee >();
employeeList.Add(new Employee("san", 1));
employeeList.Add(new Employee("Sandy", 2));
BaseComparer <Manager > objComparer2 = new BaseComparer <Manager >();
employeeList.Sort(objComparer2);
managerList.ForEach(e => Console.WriteLine(e.ID+ " " + e.Name));
}
}
If one looks at the above code, we have IComparer <in T >
as per Contravariance,we can assign Superclass Employee
to Subclass Manager [ Manger=Employee]
.
BaseComparer <Employee > objComparer1 = new BaseComparer <Employee>();
managerList.Sort(objComparer1);
Contravariance:
IComparer <Manager >.Sort (Employee)
But this reverse is invalid:
IComparer <Employee >.Sort (Manager)
The below code is referenced from 'Charlie Calvert's Community' Blog. The below code is self explanatory. I made a few modifications in terms of replacing Lamda expression with delegate full modifier declaration of readers better understanding.
namespace MainPKG
{
class Animal { }
class Cat : Animal { }
delegate T Func1 <out T >();
delegate void Action1 <in T >(T a);
class Program
{
public static Cat CreateCatInstance()
{
return new Cat();
}
public static void DisplayCatInstance(Animal animal)
{
Console.WriteLine(animal);
}
static void Main(string[] args)
{
Func1 <Cat> cat= new Func1<Cat>(CreateCatInstance);
Cat objCat = cat();
Console.WriteLine("Co:" + objCat);
Func1 <Animal> animal= delegateFunc1Cat;
Animal objAnimal = animal();
Console.WriteLine("Co:" + objAnimal);
Action1 <Animal> act1 = new Action1<Animal>(DisplayCatInstance);
act1 (new Animal());
Action1 <Cat> cat1 = act1 ;
cat1 (new Cat());
}
}
}
We can have lambda expression for the above delegate declaration to make it short. The lambda code is being referenced from Charlie's blog.
namespace MainPKG
{
class Animal { }
class Cat : Animal { }
delegate T Func1 <out T >();
delegate void Action1 <in T >(T a);
class Program
{
public static Cat CreateCatInstance()
{
return new Cat();
}
public static void DisplayCatInstance(Animal animal)
{
Console.WriteLine(animal);
}
static void Main(string[] args)
{
Func1 <Cat> cat = () => new Cat();
Func1 <Animal> animal = cat;
Action1 <Animal> act1 = (ani) => { Console.WriteLine(ani); };
Action1 <Cat> cat1 = act1;
}
}
}
If we look at the above example, what we observed is that in Covariance
subclass Cat
is assigned to Superclass Animal
, i.e., for class Cat : Animal
.
objAnimal =objCat
Whereas Contravariance is the opposite of Covariance, in this we can assign Superclass to subclass.
objCat =objAnimal
The C# variance concept is easy to understand when it comes to delegate development, but it is difficult to understand in terms of interface implementation.
I hope that I was able to meet some of your expectations, to help you understand the concept of C# variance. Any suggestions or corrections are most welcome.
- 8th June, 2010: Initial post