Friday, May 23, 2008

Covariance and Contra-variance Delegate

Delegates are pointers to the method to be invoked. In C# 2.0 new features has been introduced known as Covariance and Contra-variance.

Before we proceed consider we class hierarchy like as below

class A{}

Class B: A{}

In our program we have delegate like below

class Program
{
        public delegate A MyDelegate();
        static void Main(string[] args)
{
            MyDelegate delObj = new MyDelegate(CreateObject);
A a = delObj();
}
        public static A CreateObject()
{
return new A();
}
}

Covariance: C# 2.0 provides us flexibility that now when instantiating a delegate the specified method may also have the return type which is the derived form of the return type specified by delegate. For example, now we can instantiate the MyDelegate with a method whose return type is B.

class Program
{
        public delegate A MyDelegate();

static void Main(string[] args)
{
            MyDelegate delObj = new MyDelegate(CreateObject);       
A a = delObj();
}
        public static B CreateObject ()
{
return new B();
}
    }
This is called the covariance. It is not a problem for a compiler also because B (being sub-class of A) can easily be casted to A.
Note: When this functionality was not present in .net 1.x at that time developer need to define two delegates with different return type.


Contravariance:

Suppose we have two classes A and B with A being the parent class of B like

    class A
{
public void ADisplay()
{
Console.WriteLine("A is called...");
}
}
    class B : A
{
public void BDisplay()
{
Console.WriteLine("B is called...");
}
 }
And in our program we defined a delegate which takes a parameter of type B then with C# 1.x, we can instantiate the delegate whose signature is exactly same as that of MyDelegate() that is which takes an object of type B.

 class Program
{
        public delegate void MyDelegate(B bObj);
static void Main(string[] args)
{
            MyDelegate delObj = new MyDelegate(Fun);       
delObj(new B());
Console.ReadLine();
}

public static void Fun(B bObj)
{
bObj.BDisplay();
        }
}

When we run the program it prints the expected output

B is called...

But in C# 2.0, thanks to the contra-variance feature, we can also instantiate the delegate with a method which accepts the parameter of parent type of the type specified by the delegate. For instance in our example, we can instantiate MyDelegate() with a method which accepts an object of type A!

class Program
{
        public delegate void MyDelegate(B bObj);
static void Main(string[] args)
{
            MyDelegate delObj = new MyDelegate(MoreFun);       
delObj(new B());
Console.ReadLine();
        }
        public static void Fun(B bObj)
{
            bObj.BDisplay();
        }
        public static void MoreFun(A aObj)
{
            aObj.ADisplay();
        }
    }



When we run the program, the output is
A is called...


In fact, it is possible because the method being referred MoreFun() accepts an object of type A but the delegate accepts an object of type B. Since, B is a sub-class of A it is possible to cast an object B to type A and pass it to MoreFun().

Anyhow, covariance and contra-variance are really very useful features and it is really good to see these features added to the C# 2.0 programming language.

No comments: