I assume almost everyone reading the technical entries in this blog knows what method overriding is. Nonetheless, Method Hiding is not so widely known. I'll be focusing on C# here, as hiding instance methods in Java does not seem to exist (more on this at the end of the article).
We say a method in a derived class hides a method in the base class, when both methods have the same signature and the derived one is not overriding the base (virtual) one. Unless we want to get a compiler warning, we should use the "new" keyword in the method declaration to indicate that we intend to hide the Parent method. We have several different situations here:
- We have a non virtual method in the Base class. If we want a different behavior for that method in the Child class, we can hide it. What method will be invoked here is 100% a compile time decision, there's not vTable involved, and irrespective of the real instance held by a variable, it's the type with which that variable was declared what determines what method will be called. This is pure static dispatch.
- We have a virtual method in the Base class. If declaring the same method in the Child class with "new" instead of "override" we're somehow breaking the polymorphism. Let's say we have an instance of the Child class. If we invoke the method via a Parent variable, the Parent method is called (the entry in the Child class vTable points to the Parent method), however, if we invoke it from a variable of the Child class, no dynamic dispatch-vTable is used here, the compiler just emits a call to the method in the Child class
public class Animal{ public virtual void Walk(){ Console.WriteLine("Animal::walk"); } } public class Person: Animal{ public new void Walk(){ Console.WriteLine("Person::walk"); } } Animal a1 = new Person(); //prints Animal::Walk, so the the walk slot in Person's vTable points to the parent's method a1.Walk(); Person p1 = new Person(); //no vTable involved here, it just prints Person::Walk p1.Walk();
public class Animal{ public virtual void Run(){ Console.WriteLine("Animal::Run"); } } public class Person: Animal{ public virtual new void Run(){ Console.WriteLine("Person::Run"); } } public class Employee: Person{ public override void Run(){ Console.WriteLine("Employee::Run"); } } a1 = new Animal(); a1.Run(); a1 = new Person(); //prints Animal::Run, so the vTable slot for Person points to the parent one a1.Run(); p1 = new Person(); //prints Person::Run, so it seems like there's a second slot in the vTable p1.Run(); //polymorphism works fine using that second slot, both methods write Employee::Run p1 = new Employee(); p1.Run(); Employee e1 = new Employee(); e1.Run(); } }So, it seems like for the Person class we get 2 different slots in its vTable, one for Animal::Run and another one for Person::Run, and at invokation time the slot selected depends on the type of the variable holding the reference to the instance.
You can get the source here
And now, the natural question that stems from this: is all this Method Hiding thing any useful? (take into account that as stated above, Java seems to lack,or maybe I should say avoid, this feature). Well, in principle, I can't think of good reasons to resort to this, excepting the one explained here, that is, working around somehow the lack of return type covariance in C# methods (notice that Java has this feature). I plan to write a brief post about this topic in short.
Well, for the first case listed above, maybe it makes some sense. If someone missed declaring a method virtual, and you need to "override" it in your derived class, you still can sort of fix it by hiding it, but at the same time, this might be risky, cause not declaring it virtual could be a rather deliberate decision, so with hiding you're sort of violating the rules set by the base class designer.
For cases 2 and 3, I don't see why you could need to use this. That said, I'm not sure whether having this feature in C# is good or bad, it gives you more chances in case for some odd reason you need it, but it can also give place to more complex code and to unexpected behaviors
To complete this entry, I think we should review the main differences between C# and Java in this arena.
- By default Java methods are virtual and C# methods are non virtual. Used to dynamic languages, I think I prefer the Java approach, in fact, I think I would always go for the dynamic dispatch approach.
- There's not an equivalent to "new" in Java. If you apply the final keyword to a method in a derived class, you get a different effect from what we get in C#'s case 2. We're not breaking the polymorphism, the new method is added to the corresponding slot in the vTable, but we're preventing new derived classes from further overriding.
public class Animal{ public void walk(){ System.out.println("Animal::walk"); } } public class Person extends Animal{ public final void walk(){ System.out.println("Person::walk"); } } Animal a1 = new Person(); //prints Person::walk, so even when declared final in the child, this method seems to be added to the slot in the //Child vTable a1.walk();
No comments:
Post a Comment