Introduction
Here I am going to discuss about the Contravariance in .NET 4.0. After reading this article you will understand why we need contravariance, when and how we can use it.
Background
If you read my previous tip about Covariance then it will be easier for you to understand because
I am going to use almost the same example for contravariance. I assume that all of you know about the Action
and Func
.
Action<DeriveClass> PrintInfo;
Here PrintInfo
action can invoke those methods that have parameter DerivecCass
.
protected void PrintMyProductInfo (DeriveClass obj
DeriveClass)
So
we can easily say that
PrintInfo
can easily invoke PrintMyProductInfo .
But what if PrintInfo
wants to invoke the below method
protected void PrintMyProductInfo (BaseClass obj
BaaaseClass)
Is that possible ? Yes it's possible because of Contravariance.
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 string Name { get; set; }
public int Price { get; set; }
public string Color { 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 LcdTV : Electronic
{
public LcdTV()
{
Price = 150;
Name = " LcdTV ";
}
}
public class Laptop : Electronic
{
public Laptop()
{
Price = 250;
Name = " Laptop ";
}
}
public class Bed : Furniture
{
public Bed()
{
Price = 110;
Name = "Bed";
}
}
public class Table : Furniture
{
public Table()
{
Price = 90;
Name = "Table";
}
}
Fig 1.1 : Class Diagram
All the above code is very straightforward, just simple inheritance. Now I want to print each of the product name and price in a aspx page. And here
I have HomeAppliancePage.aspx, ElectronicCntrl.ascx, and FurnitureCntrl.ascx. Each of the control is register with the page.
<%@ Register Src="FurnitureCntrl.ascx" TagName="FurnitureCntrl" TagPrefix="uc1" %>
<%@ Register Src="ElectronicCntrl.ascx" TagName="ElectronicCntrl" TagPrefix="uc2" %>
<body>
<form id="form1" runat="server">
<uc1:FurnitureCntrl ID="FurnitureCntrl1" runat="server" />
<uc2:ElectronicCntrl ID="ElectronicCntrl1" runat="server" />
<span id="dvPrintProductInfo" runat="server"></span>
</form>
</body>
And the code is
public partial class HomeAppliancePage : System.Web.UI.Page
{
Action<HomeAppliance> PrintInfo = null;
protected void Page_Load(object sender, EventArgs e)
{
PrintInfo = this.PrintProductInfo;
}
protected void PrintProductInfo(HomeAppliance ha)
{
this.dvPrintProductInfo.InnerHtml += string.Concat("<p>",
"Product Name is ", ha.Name, " Price is ",
ha.Price.ToString(), "</p>");
}
}
HomeAppliancePage.aspx.cs has PrintProductInfo
method that will print each product name and price and the Action PrintInfo is attached with that. But
I want to use the same method for printing the Furniture and Electronic product. Below is the code:
public partial class FurnitureCntrl : System.Web.UI.UserControl
{
public Action<Furniture> PrintFurnitureInfo = null;
protected void Page_Load(object sender, EventArgs e)
{
List<Furniture> lstFurniture = new List<Furniture>() { new Bed(), new Table() };
lstFurniture.ForEach(p =>
{
PrintFurnitureInfo(p);
});
}
}
public partial class ElectronicCntrl : System.Web.UI.UserControl
{
public Action<Electronic> PrintElectronicInfo = null;
protected void Page_Load(object sender, EventArgs e)
{
List<Electronic> lstElectronic = new List<Electronic>() { new LcdTV(), new Laptop() };
lstElectronic.ForEach(p =>
{
PrintElectronicInfo(p);
});
}
}
Did you notice that both ElectronicCntrl
and FurnitureCntrl
have their own public Action
PrintElectronicInfo
and PrintFurnitureInfo
respectively.
But none of the Actions are attached with my PrintProductInfo
method of HomeAppliancePage.aspx.cs. Doing so
I need to add some code on the page load of HomeAppliancePage.aspx.cs.
protected void Page_Load(object sender, EventArgs e)
{
PrintInfo = this.PrintProductInfo;
this.FurnitureCntrl1.PrintFurnitureInfo = PrintInfo;
this.ElectronicCntrl1.PrintElectronicInfo = PrintInfo;
}
Here I am trying to do the below assgnment
Action<Derived> = Action<Base>
if you build this coe in .Net 3.5 you will receive the following error.
Cannot implicitly convert type 'System.Action<HomeAppliance>'
to 'System.Action<Furniture>' 19 51 J:\Contravarience\
Cannot implicitly convert type 'System.Action<HomeAppliance>'
to 'System.Action<Electronic>'
But in .NET 4.0 it will build successfully .
But why ?
Because in .NET 4.0 we have Contravariance.
The Action<T>
in .NET4.0 have the in
keyword . It tells the compiler that you can use either the type you specified (Furniture/Electronic) or any type that is less derived (HomeAppliance).
So it allows PrintElectronicInfo
and PrintFurnitureInfo
both of them to invoke the method
PrintProductInfo
method
of HomeAppliancePage.aspx.cs
.
Points of Interest
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<in 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 have a method to print the table information.
public string GetProductInfo(IProductItem<Table> 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:
GetProductInfo(new HouseProductItem<HomeAppliance>(450, "HomeAppliance"));-----> Less derive type
GetProductInfo(new HouseProductItem<Furniture>(350, "HomeAppliance"));---------->Less derive tye
GetProductInfo(new HouseProductItem<Table>(200, "Table"));------------->Specific Type
As because we use in
T
on my interface that is why we are
able to call it by less derive type HomeAppliance and Furniture.
As I said earlier " type you specified (Table) or any type that is less derived (HomeAppliance/Furniture)."
Hope it gives you an idea about Contravariance.