Introduction
If we think about the word Covariance in general, then we will get that it is a word that is used mostly for mathematics or statics . More specific from Wikipedia is "In probability theory and statistics, Covariance is a measure of how much two random variables change together." And in .NET 4.0 world, it also comes to measure of how much two inherited variables change together .
Background
From the polymorphism, we can write the following:
BaseClass objbaseClass = new DeriveClass();
If the above line is Ok, then what about the below code:
List<BaseClass> listObjbaseClass =new List<DeriveClass>(); ---> will give a compile error.
Why the Error?
If a=b then a+a = b+b or List<a>= List<b>
.
Yes to come across this, the Covariance is introduced in .NET 4.0.
Using the Code
Let's consider an example to make it more understandable. In my house, I have electronics and furniture. If I want to represent them through C# classes:
public abstract class HomeAppliance
{
public int Price { get; set; }
}
public abstract class ElectronicProduct : HomeAppliance
{
}
public abstract class Furniture : HomeAppliance
{
}
I have LcdTV
and Laptop
as an Electronic
items and Bed
and Table
as Furniture
items. So the classes are:
public class Bed : Furniture
{
public Bed()
{
Price = 320;
}
}
public class Table : Furniture
{
public Table()
{
Price = 120;
}
}
public class LcdTV : ElectronicProduct
{
public LcdTV()
{
Price = 100;
}
}
public class Laptop : ElectronicProduct
{
public Laptop()
{
Price = 200;
}
}
Fig 1.1 : Class Diagram
All the above code is very straight forward, just simple inheritance. Now I want the total price of my Electronic
and Furniture
products. So need a method like below:
class Utility
{
public int CalCulatePrice(List<HomeAppliance> lstHomeAppliance)
{
var total = 0;
lstHomeAppliance.ForEach(p =>
{
total += p.Price;
});
return total;
}
}
So I need total price of my Electronic
goods.
var listOfElectronicProducts = new List<ElectronicProduct>() { new LcdTV(), new Laptop() };
int totalPriceOfElectronicProducts = new Utility().CalCulatePrice(listOfElectronicProducts);
At compile time, it generates an error.
The best overloaded method match for
'Covariance.Utility.CalCulatePrice(System.Collections.Generic.List
<Covariance.HomeAppliance>)' has some invalid arguments
Argument 1: cannot convert from 'System.Collections.Generic.List
<Covariance.ElectronicProduct>' to 'System.Collections.Generic.List
<Covariance.HomeAppliance>'
Suppose it allows the above code, then what will be the problem? In my CalculatePrice
method, if I add the below line before the foreach
loop:
lstHomeAppliance.Add(new Bed());
So I am passing ElectronicProduct
list and also can add Furniture
product. If you look at the class diagram(Fig:1.1)
you will notice Electronic
product and Furniture
are in different branches, so:
List<ElectronicProduct>() { new LcdTV(), new Laptop() };
listOfElectronicProducts
will not allow any Furniture
product on it as it is a generic list for ElectronicProduct
. So I hope now you get the point. Ok, let's proceed with the discussion, did I need to change my method CalculatePrice
parameter for each of the products?
List<ElectronicProduct> or List<Furniture>
No, not at all in .NET 4.0, we have covariant
to handle this type of situations. The IEnumerable<out T> interface in .NET 4.0 has the out keyword as a parameter. It tells the compiler that you can use either the type you specified (HomeAppliance) or any type that is more derived (ElectronicProduct).
IEnumerable<out T> : IEnumerable
So if I change the CalculatePrice
parameter, it will compile and run successfully. So what's the matter, why it works not the previous one? Because when we use out
parameter at that time, we are telling the compiler that the subtype can also replace the base type here.
CalCulatePrice(IEnumerable<HomeAppliance> lstHomeAppliance)
Covariant type parameters enable the compiler to make assignments that look much
like ordinary polymorphism. Suppose you have a base class and a derived
class, named Base
and Derived
. Polymorphism enables you to assign an instance of Derived
to a variable of type Base
. Similarly, because the type parameter of the IEnumerable<T> interface is covariant
, I can assign an instance of IEnumerable<Derived>
to a variable of type IEnumerable<Base>
as shown in the following code:
IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d;
So in our scenario:
IEnumerable<ElectronicProduct> listOfElectronicProducts = new List<ElectronicProduct>();
IEnumerable<HomeAppliance> lstHomeAppliance= listOfElectronicProducts ;
List<ElectronicProduct> List<Furniture>
is a subtype of IEnumerable<HomeAppliance>
. So, each of the below code will run successfully:
var listOfElectronicProducts = new List<ElectronicProduct>() { new LcdTV(), new Laptop() };
int totalPriceOfElectronicProducts = new Utility().CalCulatePrice(listOfElectronicProducts);
var listOfFurnitures = new List<Furniture>() { new Bed(), new Table() };
int totalPricecOfFurnitures = new Utility().CalCulatePrice(listOfFurnitures);
var listOfHomeAppliance = new List<HomeAppliance>() { new Bed(), new Table(), new LcdTV(), new Laptop() };
int totalOfHomeAppliance = new Utility().CalCulatePrice(listOfHomeAppliance);
Now so far it's ok. Let's think from the point of view of Generics
. Now if I want to print the product information of each individual product. Just remove the Price
property from the HomeAppliance
and its derived classes. Let's introduce a generic interface IProductItem<T>
and add properties here.
interface IProductItem<T>
{
string Name { get; set; }
int Price { get; set; }
}
and a generic class for the product:
class HouseProductItem<T> : IProductItem<T>
{
public string Name
{
get;
set;
}
public int Price
{
get;
set;
}
public HouseProductItem(int price, string name)
{
this.Name = name;
this.Price = price;
}
}
Here, I need the below method to print the individual product information.
public string GetProductInfo(IProductItem<HomeAppliance> HomeAppliance)
{
return "Product name is " +
HomeAppliance.Name + " price is " + HomeAppliance.Price;
}
So here also, everything is simple implementation. To print, I need to call GetProductInfo:
var laptop = new HouseProductItem<Laptop>(100, "Laptop");
var info = new Utility().GetProductInfo(laptop);
But the same compilation error occurs as we experienced earlier. So what's the issue? The issue is passing more derived generic class HouseProductItem<T>
to IProductItem
<T> but did not enable the subtyping conversion using the out
parameter.
The best overloaded method match for 'Covariance.Utility.GetProductInfo
(Covariance.IProductItem<Covariance.HomeAppliance>)' has some invalid arguments
Argument 1: cannot convert from 'Covariance.HouseProductItem
<Covariance.Laptop>' to 'Covariance.IProductItem<Covariance.HomeAppliance>'
So a small change can make all the code error free and that is:
interface IProductItem<out T>
{
string Name { get; set; }
int Price { get; set; }
}
Now it will allow more derived class as a parameter. Hope now it's more clear to you what covariance is all about.
Points of Interest
Rather than using an List<T>
or IEnumerable<T>
if someone wants to use simple Array
, then we get a compilation error for that. Let's see.
public int CalCulatePrice(HomeAppliance[] homeApplicance)
{
var total = 0;
homeApplicance[0] = new Laptop();
homeApplicance.ToList().ForEach(p =>
{
total += p.Price;
});
return total;
}
var arrayOfFurniture = new Furniture[] { new Bed(), new Table() };
int totalPricecOfFurnitures = new Utility().CalCulatePrice(arrayOfFurniture);
This will not give any compilation error, unfortunately the runtime error will be thrown.
Attempted to access an element as a type incompatible with the array.
So while assigning more derived to less derived class, try to use IEnumerable<out T>
in .NET 4.0.