Ideas, designs and solutions with explanations and examples.

Monday, July 7, 2008

Method Hiding - Feature or Bug in Compiler?

Since its very beginning C# has a 'method hiding' feature, which lets us 'hide' base class non-virtual methods. In other words one can write:
class Base
{
public void Test() {
Console.WriteLine("Base");
}
}

class Derived : Base
{
public new void Test() {
Console.WriteLine("Derived");
}
}

//...

Derived d = new Derived();
d.Test(); // "Derived" is printed


So far so good, but what happens if I write:



Base d = new Derived();
d.Test(); // "Base" is printed


Is this an intended behavior? Well, may be yes, but may be no. In case of 'no', we just called a wrong function, and no warning or error was issued by the compiler... Just consider again what happened, we declared a non-virtual method - that's our contract that should be ensured by the compiler. Instead it provides us with a feature to break it.



That's not all, consider the situation where you have some 3rd party library and use it in your project. Now the vendor releases a new version where he hides some method and provides an alternative implementation. Your program will still call the old method until you recompile! So in case you have several modules some recompiled and some not, you may have inconsistent behavior...



Of course you can always find use-cases, where the feature is carefully used and actually is very useful in that context. Yet its disadvantages outweight the advantages, so I think it would be better if we will develop our programs without using it.



Unfortunately there is an additional very similar "feature" - private interfaces, which can be implemented by a type even if its base class already implemented it and declared it sealed. Again the language helps us to break the contract and if it's not done carefully enough, the bugs won't wait to come...

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