Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Contravariance in C#

0.00/5 (No votes)
12 Aug 2013 1  
Understand why we need Contravariance in C#.

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  objDeriveClass)  

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  objBaaaseClass)  

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here