Friday, July 4, 2008

C# Co[ntra]variance

To get introduced to the topic, please read Eric Lippert's variance series first.

Here I would like to present my solution to the problem.

After all there are only 3 possibilites:

  1. Clear covariance (IEnumerator<T>).
  2. Clear contravariance (IComparer<T>).
  3. Undecidable (IList<T>).

All the 3 possibility are easily detectable by compiler and I believe for the first two we would like the compiler will decide the variance automatically. We need a solution for the third case only.

To see the solution, I would like first analyze how we selected which case the given interface belongs to. We analyzed the method signatures in which T is involved:

  1. If in all of them T is 'out' parameter, the interface is covariant.
  2. If in all of them T is 'in' parameter, the interface is contravariant.
  3. If there is a mix - undecidable.

My solution is to say the compiler how I'm going to use my variable and enable either covariant or contravariant invocations:

IList<Animal> aa = whatever;
IList<in Giraffe> ag = aa;
IList<out Giraffe> agX = aa; //fails to compile

ag user sees: 'int ag.IndexOf(Giraffe)' and 'object ag[int]'.

IList<Giraffe> ag = whatever;
IList<out Animal> aa = ag;
IList<in Animal> aaX = ag; //fails to compile

aa user sees: 'int aa.IndexOf(<only null can be passed>)' and 'Animal aa[int]'.

Now I can say in a type safe manner:



class X<T> {

T _t;

void Test(IList<out T> at) {
T t = at[0]; //clearly t is 'out' parameter
}

void Test(IList<in T> at) { //overloaded method!
int i = at.IndexOf(_t); //clearly _t is 'in' parameter
}
}

//...

new X<Animal>().Test(new List<Giraffe>()); //first method is called
new X<Giraffe>().Test(new List<Animal>()); //second method is called

No comments: