I’ve found loads of practical examples of this, and understand the practical output when overriding or hiding methods, but I’m looking for some under the covers info on why this is and why C# allows it when according to the rules of polymorphism, this shouldn’t be allowed – at least, insofar as my understanding of polymorphism goes (which seems to coincide with the standard definitions found on Wikipedia/Webopedia).
Class Base
{
public virtual void PrintName()
{
Console.WriteLine("BaseClass");
}
}
Class FirstDerived : Base
{
public override void PrintName()
{
Console.WriteLine("FirstDerived");
}
}
Class SecondDerived : Base
{
public new void PrintName()
{
Console.WriteLine("SecondDerived");
}
}
Using the following code:
FirstDerived b = new FirstDerived();
BaseClass a = b;
b.PrintName();
a.PrintName();
I get:
FirstDerived
FirstDerived
Okay, I get that, makes sense.
SecondDerived c = new SecondDerived();
BaseClass a = c;
c.PrintName();
a.PrintName();
I get:
SecondDerived
BaseClass
Okay, that makes sense, too, instance a can’t see c.PrintName() so it’s using its own method to print its own name, however I can cast my instance to its true type using:
((SecondDerived)a).PrintName();
or
(a as SecondDerived).PrintName();
to get the output I would expect:
SecondDerived
So what is going on under the covers and what does this mean in terms of polymorphism? I’m told that this facility “breaks polymorphism” – and I guess according to the definition, it does. Is that right? Would an “object oriented” langage like C# really allow you to break one of the core principles of OOP?
(This answers the “why is it allowed” which I think is really the central point of your question. How it works in terms of the IL is less interesting to my mind… let me know if you want me to go into that though. Basically it’s just a case of specifying the method to call with a different type token.)
It allows base classes to evolve without breaking derived classes.
Suppose
Basedidn’t originally have thePrintNamemethod. The only way to get atSecondDerived.PrintNamewould be to have an expression with a static type ofSecondDerived, and call it on that. You ship your product, everything is fine.Now fast forward to
Baseintroducing aPrintNamemethod. This may or may not have the same semantics ofSecondDerived.PrintName– it’s safest to assume that it doesn’t.Any callers of
Base.PrintNameknow that they’re calling the new method – they couldn’t have called it before. Any callers which were previously usingSecondDerived.PrintNamestill want to use it though – they don’t want to suddenly end up callingBase.PrintNamewhich could do something entirely different.The difficulty is new callers of
SecondDerived.PrintName, who may or may not appreciate that this isn’t an override ofBase.PrintName. They may be able to notice this from the documentation of course, but it may not be obvious. However, at least we haven’t broken existing code.When
SecondDerivedis recompiled though, the authors will be made aware that there’s now aBase.PrintNameclass through a warning. They can either stick to their existing non-virtual scheme by adding thenewmodifier, or make it override theBase.PrintNamemethod. Until they make that decision, they’ll keep getting a warning.Versioning and compatibility isn’t usually mentioned in OO theory in my experience, but C# has been designed to try to avoid compatibility nightmares. It doesn’t solve the problem completely, but it does a pretty good job.