Click here to Skip to main content
11,583,484 members (65,351 online)
Click here to Skip to main content

Covariance in C#

, 4 Aug 2013 CPOL 13.8K 21
Rate this:
Please Sign up or sign in to vote.
Understand the use of covariance in .NET 4.0

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);
//Output : 300
var listOfFurnitures = new List<Furniture>() { new Bed(), new Table() };
int totalPricecOfFurnitures = new Utility().CalCulatePrice(listOfFurnitures);
//Output:440
var listOfHomeAppliance = new List<HomeAppliance>() { new Bed(), new Table(), new LcdTV(), new Laptop() };
int totalOfHomeAppliance = new Utility().CalCulatePrice(listOfHomeAppliance);
//Output:740  

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Faisal(mfrony)
Bangladesh Bangladesh
I am a Sr.Software Engineer at Brain Station -23. I have 5+ years of work experience in .Net technology. I strongly believe that before software can be reusable it first has to be usable.

My contact info :

mfrony2003@yahoo.com
mfrony2003@hotmail.com

LinkedIn
http://www.linkedin.com/profile/view?id=106671466&trk=tab_pro

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 5 Pin
Md Nazmoon Noor14-May-14 3:52
memberMd Nazmoon Noor14-May-14 3:52 
GeneralMy vote of 3 Pin
HarisJayadev3-Sep-13 18:13
memberHarisJayadev3-Sep-13 18:13 
GeneralMy vote of 5 Pin
badashell5-Aug-13 7:28
memberbadashell5-Aug-13 7:28 
GeneralMy vote of 3 Pin
suday4-Aug-13 21:54
membersuday4-Aug-13 21:54 
SuggestionArticle more like... Pin
Vitaly Tomilov4-Aug-13 11:52
memberVitaly Tomilov4-Aug-13 11:52 
GeneralRe: Article more like... Pin
Faisal(mfrony)4-Aug-13 21:37
memberFaisal(mfrony)4-Aug-13 21:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150603.1 | Last Updated 4 Aug 2013
Article Copyright 2013 by Faisal(mfrony)
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid