Click here to Skip to main content
Click here to Skip to main content

Variance in C#.NET 4.0 Covariance and Contravariance

, 8 Jun 2010
Rate this:
Please Sign up or sign in to vote.
Conceptual Understanding of Variance in C#.NET Covariance and Contravariance

Table of Contents

  1. Introduction
  2. Background
  3. Prerequisites
  4. Rules for Variance in C# 4.0
  5. Generic Interface: Covariance and Contravariance
  6. Real Time Example: Contravariance
  7. Generic Delegate: Covariance and Contravariance
  8. References
  9. Conclusion
  10. History

Introduction

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.

Background

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.

Rules For Variance in C# 4.0

  • 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.

Prerequisites

  • C# .NET 4.0

Generic Interface :Covariance and Contravariance

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

        /*
        //We can make below method Virtual if we've opted Explicit Implementation
        public virtual void SendData(T data)
        {
            this.m_Data = data;
        }
         */
    }
class Program
    {
        static void Main(string[] args)
        {        
            //Covariance
	   //Compiles in C# .net 4.0            
            IMessageSend <object> objMessageSend =  new DataTX <string>();     
            
            //Contravariance
	   //Compiles in C# .net 4.0
            IMessageSend <string> strMessageSend = new DataTX <object>();          
        }
    }        

Real Time Example: Contravariance

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

            //Runs as per Contravariance Rule.
            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));
            
            //This will throw compile time ERROR ..
            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) 

Generic Delegate :Covariance and Contravariance

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)
        {          
            // Covariance
            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);

            // Contravariance
            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)
        {          
            // Covariance
            Func1 <Cat> cat = () => new Cat();
            Func1 <Animal> animal = cat;

            // Contravariance
            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.

References

Conclusion

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.

History

  • 8th June, 2010: Initial post

License

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

Share

About the Author

santosh poojari
Technical Lead
India India
Whatsup-->Exploring--> MVC/HTML5/Javascript & Virtualization.......!
www.santoshpoojari.blogspot.com

Comments and Discussions

 
Questiongood explanation Pinmemberindrajeet_pat12318-Feb-14 23:03 
QuestionCode having typo error PinmemberMember 103861947-Nov-13 0:50 
Questionthere is a slight error at the start of the tutorial Pinmemberyaliyali1-Dec-12 23:33 
QuestionTypo or Error? Pinmembersobo12317-Dec-11 15:28 
GeneralMy vote of 5 PinmemberDukhabanndhu Sahoo27-Oct-11 17:59 
Clear explanation

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 | Mobile
Web02 | 2.8.140827.1 | Last Updated 8 Jun 2010
Article Copyright 2010 by santosh poojari
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid