Covariance and Contra variance in .Net 4.0 Framework

Covariance and Contra variance in Delegates

There has always been confusion for me to understand the concept of Covariance and Contra variance. And today when I scaled down to this definition and example to understand them, it has become really easy to understand it. So here I am sharing with you all.

Covariance: In earlier versions of Visual C#, a delegate method must be exactly matched to a delegate signature. To create a successful match, the parameter type and the return type of the delegate method must be identical to the parameter type and the return type of the delegate signature.
When we talk about covariance in .net, we say it permits a method to have a more derived return type than what is defined in the delegate. In Visual C# 2005, you may notice that more than one match can be established when you work in an inheritance context. In this scenario, delegate behaviour may change.

using System;
delegate object D();
class A {
public object M() {return "object";}
}
class B:A {
public string M() {return "string";}
}
public class MyClass {
public static void Main() {
B b = new B();
D d = new D(b.M);
Console.WriteLine(d());
}
}

When you compile and run this code sample in the Microsoft .NET Framework 2.0, you receive a result of “String”. In the .NET Framework 2.0, both the M method in the base class and the M method in the derived class can be matched to the delegate signature. However, overload resolution rules decide that the one method in the most specific class wins the match. In this example, the method that is in class B wins the match.
Covariant type parameters enable you to make assignments that look much like ordinary polymorphism. Polymorphism enables you to assign an instance of derived class to a variable of type base class. Similarly, because the type parameter of the IEnumerable<T> interface is covariant, you can assign an instance of IEnumerable<derived class> to a variable of type IEnumerable<base class>, as shown in the following code.

IEnumerable<Dogs> d = new List<Dogs>();
IEnumerable<Mammals> m = d;

This gave an error “Cannot implicitly convert type” in Framework 2.0. But it compiles without error in Framework 4.0.  The List<T> class implements the IEnumerable<T> interface, so List<derived class> implements IEnumerable<derived class>. The covariant type parameter does the rest.

Contra variance: When a delegate method signature has one or more parameters of types that are derived from the types of the method parameters, that method is said to be contra variant. As the delegate method signature parameters are more specific than the method parameters, they can be implicitly converted when passed to the handler method. Contra variance therefore makes it easier to create more general delegate methods that can be used with a larger number of classes.

class Animal
{}
class Dog : Animal
{}
class DelgateDemo
{
public delegate void getAction(Dog dog);
public static void Run(Animal cat)
{}
public static void Eat(Dog petdog)
{}
static void Main(string[] args)
{
getAction g1 = new getAction(Run);
getAction g2 = new getAction(Eat);
}
}

Now have you ever imagined that IComparer<object> and IComparer<string> are one and the same? It is possible in Framework 4.0 because the interface IComparer<T> has become IComparer<in T>. The “in” indicates that the parameters are restricted to occur only in input positions. Let’s go for an example to explain the contra variance in Framework 4.0.

public class Animal
{
public int Weight { get; set; }
public string Name { get; set; }
public Animal()
{ }
public Animal(string InputName, int InputWeight)
{
Name = InputName;
Weight = InputWeight;
}
}
public class Elephant : Animal
{
public Elephant(string InputName, int InputWeight) : base(InputName, InputWeight)
{   }
}

To weigh animals, we will create WeightComparer which will implement IComparer interface; which in turn will enable us to use the Sort() method on list object.

public class WeightComparer : IComparer<Animal>
{
public int Compare(Animal x, Animal y)
{
if (x.Weight > y.Weight) return 1;
if (x.Weight == y.Weight) return 0;
return -1;
}
}

Now we will check by adding making a list of animals and sorting it and making a list of elephants and sorting it using the Sort() method of IComparer.

static void Main(string[] args)
{
WeightComparer objAnimalComparer = new WeightComparer();
List<Animal> Animals = new List<Animal>();
Animals.Add(new Animal("elephant", 500));
Animals.Add(new Animal("tiger", 100));
Animals.Add(new Animal("rat", 5));

Animals.Sort(objAnimalComparer);

List<Elephant> Elephants = new List<Elephant>();
Elephants.Add(new Elephant("Nellie", 100));
Elephants.Add(new Elephant("Dumbo", 200));
Elephants.Add(new Elephant("Baba", 50));

Elephants.Sort(objAnimalComparer);

Elephants.ForEach(e => Console.WriteLine(e.Name + " " + e.Weight.ToString()));
}

In the above code prior to .Net 4.0 Elephants list could not have been sorted it gives error “Cannot convert from ‘WeightComparer’ to ‘System.Collections.Generic.IComparer<Elephant>”.
Above samples have cleared the concepts of covariance and contra variance; also have explained the enhancement in these concepts in .Net Framework 4.0.

 

Leave a reply