Replacing Methods in a Derived Class
Now that you have seen the high-level motivation for polymorphism, it's time for a low-level explanation of the mechanics of how members are accessed at runtime. To use inheritance to add polymorphic behavior to your designs, you must first understand the differences between static binding and dynamic binding.
Static binding and dynamic binding represent two different ways in which a client can invoke a method or property of an object. Static binding is usually more straightforward and results in better performance. Dynamic binding yields greater flexibility and is the underlying mechanism that allows a derived class to replace behavior inherited from a base class. The latter type of binding is one of the crucial ingredients of polymorphic programming.
Listing 5.3 A simple base class with a statically bound method
Public Class Human Public Function Speak() As String Return "I am a human" End Function End Class Public Class Programmer : Inherits Human '*** inherits Speak from base class End Class
First, let's tackle static binding. Suppose you are designing the Human and Programmer classes and come up with the two class definitions shown in Listing 5.3. Now imagine someone writes code that creates a Programmer object and invokes the Speak method. The caller can invoke the Speak method through a reference variable of type Programmer or through a reference variable of type Human:
Dim programmer1 As Programmer = New Programmer() Dim human1 As Human Dim message1, message2 As String message1 = programmer1.Speak() '*** calls Human.Speak() message2 = human1.Speak() '*** calls Human.Speak() Console.WriteLine(message1) '*** "I am a human" Console.WriteLine(message2) '*** "I am a human"
Both method invocations work via static binding, which is the default method invocation mechanism used by Visual Basic .NET and by the CLR. Static binding is based on the type of the reference variable, not the type of object being referenced. For example, if you are accessing a Programmer object through a reference variable of type Human, the type information from the definition of Human is used to bind the client to the correct member definition.
Another important aspect of static binding is that the decision about where to locate the definition for the method being invoked is always made at compile time—that is, statically. This decision-making process is different than in dynamic binding, in which the decision about where to locate the method definition is made at runtime. With static binding, the compiler can perform more of the work at compile time. For this reason, static binding can provide a measurable performance advantage over dynamic binding.
Dynamic Binding and Overridable Methods
One of the most valuable features of inheritance is the fact that the designer of a base class can provide default behavior that can optionally be replaced by a derived class author. For example, a base class author can provide a default implementation for a method. A derived class author can then choose whether to use the base class implementation or to write a more specialized implementation. In some cases, a derived class author even has the option of extending the base class implementation by adding extra code to the derived class.
Using a derived class to replace the implementation of a method or a property defined in a base class is known as overriding. Method overriding relies on dynamic binding, not static binding. Dynamic binding takes into account the type of an object at runtime, which gives the CLR a chance to locate and call the overriding method. In this way dynamic binding supports more flexible code, albeit with a runtime cost. Interestingly, dynamic binding is the default in the Java programming language and, in fact, the only option in Java in most cases.
An example will illustrate why dynamic binding is so important. Imagine you have written the following client-side code that uses the programming contract defined by the Human class shown in Listing 5.3:
Public Class Reporter Public Sub InterviewHuman(ByVal target As Human) Dim Message As String = target.Speak() MessageBox.Show(Message) End Sub End Class
A caller could invoke the InterviewHuman method by passing a Human object or by passing a Programmer object. Whichever type of object is passed to the InterviewHuman method must, however, provide an implementation of the Speak method as defined by the programming contract of the Human class.
An important aspect of polymorphism is that any Human-derived class should be able to provide a behavior for the Speak method that differs from the behavior of other Human-derived classes. For example, a Programmer class should be able to provide an implementation of Speak that is different from that in the Human class. You cannot take advantage of these different behaviors if static binding is being used. Because the InterviewHuman method programs against the Human type, static binding would result in every call to Speak being dispatched to the implementation defined in the Human class. Therefore, true polymorphic behavior is not possible with static binding. Your class design must contain methods that are invoked through dynamic binding.
Of course, the features of dynamic binding don't apply to all kinds of class members. When you add methods and properties to a base class, you have the option of defining them to be invoked through either static binding or dynamic binding. You don't have the same option when you add fields to a base class. In the CTS, fields can be accessed only through static binding. In other words, method and properties can be declared as overridable but fields cannot. This restriction gives public methods and properties yet another advantage over public fields from a design perspective.
Now that you've learned the fundamental concepts behind dynamic binding, it's time to see the Visual Basic .NET syntax that's required to support it. You enable dynamic binding by defining overridable methods and properties.
Listing 5.4 A simple base class with a dynamically bound method
Public Class Human Public Overridable Function Speak() As String '*** default implementation Return "I am a human" End Function End Class
The first requirement to enable overriding is that a base class must define the method or property as overridable. To do so, you declare the member definition using the Overridable keyword (equivalent to the virtual keyword in C# and C++), as shown in Listing 5.4. It's important to understand the implications of defining a method with the Overridable keyword. In this case, it means that every invocation of the Speak method through a reference variable of type Human will result in dynamic binding. Also, classes that inherit from Human will have the option of overriding the Speak method to provide a more specialized implementation.
Because a dynamically bound call is potentially slower than a statically bound call, it makes sense that a base class author must ask for it explicitly. Languages such as Visual Basic .NET and C# require a base class author to be very explicit about declaring methods that are overridable for another reason, too: When a method is overridable, the design becomes more challenging because the method overriding complicates the contract between a base class and its derived classes. We will revisit this topic later in this chapter. For now, just take it on faith that declaring a method or property as overridable increases your responsibilities as a base class author.
Let's finish our example by creating a derived class that overrides a method implementation defined within its base class. First, the derived class must contain a method with the same name and the same signature as the overridable method in its base class. Second, the overriding method must be explicitly declared to override the base class implementation using the Overrides keyword. In the following code fragment, the Programmer class overrides the Speak method inherited from the Human class of Listing 5.4:
Public Class Programmer : Inherits Human Public Overrides Function Speak() As String '*** overriding implementation Return "I am a programmer" End Function End Class
Now that we've written a derived class definition that overrides a method implementation in its base class, we are ready to see an example of dynamic binding in action. Examine the following client-side code:
Dim programmer1 As Programmer = New Programmer() Dim human1 As Human = programmer1 Dim message1, message2 As String message1 = programmer1.Speak() '*** calls Programmer.Speak message2 = human1.Speak() '*** calls Programmer.Speak Console.WriteLine(message1) '*** "I am a programmer" Console.WriteLine(message2) '*** "I am a programmer"
As this code demonstrates, it doesn't matter whether you access the Programmer object through a reference variable of type Programmer or of type Human. The dynamic binding scheme employed by the CLR always locates the appropriate method implementation by looking up the inheritance hierarchy for the most-derived class that holds a definition for the method in question. In the preceding code, Programmer is the most-derived class that contains an implementation of the Speak method.
Chaining Calls from a Derived Class to a Base Class
When you override a method, it's fairly common practice to chain a call from your overriding implementation in the derived class to the overridden implementation in the base class. This technique allows you to leverage the implementation provided by the base class and extend it with extra code written in the derived class. Consider the following reimplementation of the Programmer class:
Public Class Programmer : Inherits Human Public Overrides Function Speak() As String '*** chain call to Speak method in base class Return MyBase.Speak() & " who is a programmer" End Function End Class
The Visual Basic .NET keyword MyBase is used in a derived class to explicitly access public or protected members in its base class. In this example, the Programmer definition of Speak makes an explicit call to the Human implementation of Speak. This approach allows the derived class author to reuse and extend the method implementation provided by the base class author.
As shown in the preceding example, the MyBase keyword allows a derived class author to chain a call to the base class author's implementation. A chained call doesn't have to be made at the beginning of the derived class implementation, however. It can be made at any point in the overriding implementation.
Design Issues with Overridable Methods
You've just seen the syntax for creating overridable methods. You've also seen the syntax for overriding a method and for chaining a call to an overridden base class implementation. As a result of the discussion, you might have concluded that the syntax required for method overriding isn't especially complicated.
In reality, mastering the syntax is the easy part. Making sure you get the semantics correct is a much tougher job. Anyone who has managed a large software project using inheritance and method overriding can tell you that managing the semantics of overridable methods and properties requires a high level of expertise and attention to detail.
An overridable method complicates the programming contract of a base class because a derived class author can use any of three possible approaches:
A derived class author can inherit a base class implementation and reuse it without modification.
A derived class author can provide an overriding implementation that chains a call back to the base class implementation.
A derived class author can provide an overriding implementation that does not chain a call back to the base class implementation.
Consider these three approaches for dealing with an overridable method from a design perspective. You might say that there are really three options: reusing, extending, and replacing. When a derived class inherits a method, it reuses the base class implementation. When a derived class overrides a method and chains a call back the base class, it extends the base class implementation. When a derived class overrides a method and does not chain a call back the base class, it replaces the base class implementation.
While the CLR's support for method overriding allows for reusing, extending, and replacing, many overridable methods have semantics that do not support all three approaches. The overridable Finalize method of the Object class, for instance, is a real-world example of an overridable method that does not allow replacing. If you elect to override the Finalize method in a user-defined class, your implementation must chain a call back to the Finalize implementation of the base class. If you fail to chain a call to the base class implementation, you have broken the semantic contract of this overridable method and your code will likely exhibit problematic behavior. Chapter 10 discusses when and how to override the Finalize method; for now, just recognize that replacing the implementation for an overridable method creates problems.
As you can see, some overridable methods only support reusing or extending the base class implementation. An overridable method may also have semantics that allow for reusing and replacing yet disallow extending. In general, the semantics of overridable methods and properties require extra attention.
The semantics involved with chaining can become even more complicated because the semantics of some overridable methods require an overriding implementation to chain a call back to the base class implementation at a specific point in time. For example, the semantics of one overridable method might require overriding method implementations to chain a call to the base class implementation before doing any work in the derived class. The semantics of another overridable method might require overriding method implementations to chain a call to the base class implementation after all work has been completed in the derived class implementation.
This discussion should lead you to two important observations:
The semantics of method and property overriding are often sensitive to whether an overriding method should chain a call to its base class.
The semantics of overriding can be affected by whether the chained call should be made at the beginning or at the end of the overriding method or property implementation.
If you must ever design a base class, it is your responsibility to document the semantics for each overridable method and property. Your documentation should specify for each overridable method and property whether chaining a call back to your base class implementation is required. You should also point out any occasion where a chained call must be made at the beginning or at the end of the overriding implementation in the derived class.
Even if you never design or write a base class definition, you must keep these rules in mind. As a .NET programmer, you will almost certainly encounter situations in which you must create classes that inherit from one of the base classes provided by the .NET Framework.
When you are working with inheritance, semantic errors can be much more challenging to find than syntax errors. The compiler will catch syntax errors and identify their exact locations in your code, but it cannot catch semantic errors. This factor makes semantic errors related to inheritance far more difficult to locate. Making sure the semantics for overridable methods are well defined and adhered to requires a lot of discipline. It may also require coordination across different development teams.
Declaring a Method as NotOverridable
Recall that a class created with Visual Basic .NET is inheritable by default. If you create a class named Programmer that inherits from Human, another programmer can create a third class, SeniorProgrammer, that inherits from your derived class:
Public Class SeniorProgrammer : Inherits Programmer '*** can this class override Speak? End Class
Given the class definitions for Human, Programmer, and SeniorProgrammer (which now form the inheritance hierarchy shown in Figure 5.1), ask yourself the following question: Should the author of SeniorProgrammer be able to override the Programmer implementation of Speak? The answer is yes. A method that is declared with the Overrides keyword is itself overridable. The author of SeniorProgrammer can override the implementation of Speak in Programmer with the following code:
Public Class SeniorProgrammer : Inherits Programmer Public Overrides Function Speak() As String '*** overriding implementation End Function End Class
You can take this example one step further by creating a class named SuperSeniorProgrammer that inherits from SeniorProgrammer. SuperSeniorProgrammer would be able to override the SeniorProgrammer definition of the Speak method with yet another implementation.
If you take this example to the logical extreme, you can create as many classes as you want in the inheritance hierarchy, with each class inheriting from the one above it and overriding the Speak method with a new implementation. There isn't really a theoretical limitation on how many levels you can design in an inheritance hierarchy. In reality, practical limitations often determine how many levels of inheritance you should allow. A few examples will demonstrate how you can limit the use of inheritance to keep a complicated design from getting out of hand.
Suppose you've created a definition for Programmer by inheriting from Human. From your perspective, you are the beneficiary of inheritance because you were able to reuse code from Human and you saved yourself a good deal of time in doing so. However, if you allow other programmers to inherit from your derived class, you must also live up to the responsibilities of a base class author. That includes documenting the semantics for all of your overridable methods.
When you override a method using the Overrides keyword, your method definition becomes overridable by default. You can reverse this default behavior by adding the NotOverridable keyword before the Overrides keyword. This technique is used here to prevent the continued overriding of the Speak method:
Public Class Programmer : Inherits Human Public NotOverridable Overrides Function Speak() As String '*** overriding implementation End Function End Class Class SeniorProgrammer : Inherits Programmer '*** this class cannot override Speak End Class
The author of SeniorProgrammer is no longer allowed to override the Speak method. As this example illustrates, when you declare an overriding method implementation with the NotOverridable keyword, that choice simplifies your design. You don't have to worry about other classes inheriting from your class and breaking the semantics of your method.
Using the NotOverridable keyword allows you to disallow overriding on a method-by-method or a property-by-property basis, but another option can make life even easier. Recall that you can disallow inheriting altogether by using the NotInheritable keyword. This keyword is applicable to base classes as well as derived classes such as Programmer:
Public NotInheritable Class Programmer : Inherits Human Public Overrides Function Speak() As String '*** overriding implementation End Function End Class
Now classes may no longer inherit from Programmer. This choice really simplifies things because you don't have to worry about a contract of behavior between Programmer and derived classes. Sometimes it makes sense to define overridden methods and properties as NotOverridable; at other times it's better to define a derived class as NotInheritable. Both techniques simplify the overall design of a derived class.
Most software developers agree that keeping a design as simple as possible is beneficial. But there's another good reason to apply the NotOverridable and NotInheritable keywords whenever you can: They can also improve performance.
Recall that overridable methods require the use of dynamic binding and, therefore, may incur a runtime cost. Judicious use of the NotOverridable and NotInheritable keywords allows the Visual Basic .NET compiler to employ static binding rather than dynamic binding at times, thereby reducing execution time.
For example, imagine Programmer is defined with the NotInheritable keyword. The Visual Basic .NET compiler can make the assumption that a reference variable of type Programmer will only reference an object created from the Programmer class. That is, the client will never use a Programmer reference variable to access an object of some class derived from Programmer. Because Programmer is sealed, a Programmer reference variable can only be used to access objects created from Programmer. There is no opportunity for polymorphism and, consequently, no need to use dynamic binding. In such a case, the compiler will optimize calls by using static binding instead of dynamic binding.
MyBase versus MyClass versus Me
While we're on the topic of static binding versus dynamic binding, it makes sense to discuss some subtle differences between the keywords Me, MyClass, and MyBase. All three can be used inside a method implementation of a class to refer to a class member, but they can exhibit quite different behavior.
Listing 5.5 summarizes the class definitions we have discussed so far: Human, Programmer, and SeniorProgrammer. Study the listing, and determine which methods are invoked using static binding and which are invoked using dynamic binding.
Listing 5.5 An inheritance hierarchy with statically and dynamically bound methods
Public Class Human Public Overridable Function Speak() As String Return "I am a human" End Function End Class Public Class Programmer : Inherits Human Public Overrides Function Speak() As String Return "I am a programmer" End Function Public Sub GetInfo() '*** what happens when you call Speak from this method? End Sub End Class Public Class SeniorProgrammer : Inherits Programmer Public Overrides Function Speak() As String Return "I am a senior programmer" End Function End Class
Listing 5.5 includes three different definitions of the Speak method. The Programmer class overrides the definition of Speak from its base class, then is itself overridden again by the derived class SeniorProgrammer. Notice that the Programmer class now contains an additional method named GetInfo. Imagine you wrote the following definition for this method:
'*** method definition in Programmer class Public Sub GetInfo() Dim message1, message2, message3, message4 As String message1 = MyBase.Speak() message2 = MyClass.Speak() message3 = Me.Speak() message4 = Speak() Console.WriteLine(message1) '*** ? Console.WriteLine(message2) '*** ? Console.WriteLine(message3) '*** ? Console.WriteLine(message4) '*** ? End Sub
As you can see, there are four different ways to call the Speak method. The question is, What does the method output? The answer: The output depends on the type of object. First, suppose you call GetInfo using a reference variable of type Human:
Dim h1 As Human = New Human h1.GetInfo()
This code fails to compile because the Human class does not contain a method called GetInfo—just making sure you were awake! Next, suppose you call GetInfo using a reference variable of type Programmer that refers to a Programmer object:
Dim p1 As Programmer = New Programmer p1.GetInfo()
The method call outputs the following to the console window:
I am a human I am a programmer I am a programmer I am a programmer
That should make sense, because the base class of a Programmer is Human. Finally, suppose you call GetInfo using a reference variable of type Programmer that refers to a SeniorProgrammer object:
Dim p2 As Programmer = New SeniorProgrammer() p2.GetInfo()
Here is the resulting output:
I am a human I am a programmer I am a senior programmer I am a senior programmer
The explanation of this result is a little more subtle. While the object is of type SeniorProgrammer, the method being called is defined inside the Programmer class. Therefore, this example illustrates a case where the Programmer class has other classes both above it and below it in the inheritance hierarchy that can affect what happens at runtime.
What happens when this call to p2.GetInfo executes?
When GetInfo calls MyBase.Speak, the Visual Basic .NET compiler uses static binding to invoke the implementation of Speak within the base class—in this case the Human class, because Programmer inherits from Human.
When it calls MyClass.Speak, the compiler use static binding to invoke the implementation of Speak in the calling method's class—in this case Programmer because GetInfo is defined within Programmer.
When it calls Me.Speak, the compiler uses dynamic binding to invoke the most-derived implementation of Speak—in this case it is defined in SeniorProgrammer.
If you call Speak without using one of these three keywords, it has the exact same effect as calling Me.Speak—namely, it uses dynamic binding.
Calls through the MyBase and MyClass keywords always result in static binding to the base class and the current class, respectively. Calls through the Me keyword result in dynamic binding whenever the method being called is declared as overridable. Each of these keywords can be useful in certain scenarios.
While most uses of static binding are relatively straightforward, this is not always the case. In certain situations, static binding can become complex and non-intuitive. In particular, it can be tricky when a base class and a derived class have one or more member definitions with the same name. An example will demonstrate this point.
Suppose we return to Listing 5.3, where the Human class defines Speak as a statically bound method. What would happen if the Programmer class also supplied a method called Speak? In other words:
Public Class Human Public Function Speak() As String Return "I am a human" End Function End Class Public Class Programmer : Inherits Human Public Function Speak() As String Return "I am a programmer" End Function End Class
Both class definitions contain a method named Speak with the same calling signature. When a member in a derived class is defined in this manner with the same name as a non-overridable member in its base class, the technique is called member shadowing. That is, the Programmer class definition of Speak shadows the Human class definition of Speak.
Listing 5.6 A derived class that shadows a method of its base class
Public Class Human Public Function Speak() As String Return "I am a human" End Function End Class Public Class Programmer : Inherits Human Public Shadows Function Speak() As String Return "I am a programmer" End Function End Class
The Visual Basic .NET compiler produces a compile-time warning when you shadow an inherited member. This warning is meant to raise a red flag so you can avoid shadowing if you have stumbled upon it accidentally. If you want to deliberately shadow a member from a base class, you can suppress the compiler warning by making your intentions explicit with the Shadows keyword, as shown in Listing 5.6.
In a few rare situations, an experienced class designer may decide to use shadowing. The most common scenario where shadowing occurs is when the base class author adds new members to a later version. Imagine that you created the Programmer class by inheriting from an earlier version of the Human class that did not contain a Speak method. Therefore, at the time when you added the public Speak method to the Programmer class, it did not conflict with any of the methods inherited from its base class.
What would happen if the author of the Human class decided to add a public Speak method in a later version of the class? You would then face the dilemma of either removing the Speak method from the Programmer class or shadowing the Speak method from the Human class. A few other scenarios call for shadowing, but this one is probably the most common.
You should do your best to avoid shadowing members from a base class, because member shadowing creates ambiguities that make it easy for a client-side programmer to get into trouble. The problem with member shadowing is that it is based on static binding and, consequently, produces inconsistent results.
The following example will demonstrate where shadowing a member in a base class can create a good deal of confusion. Imagine you're writing client-side code in which you will create an object of type Programmer. Assume the Programmer class is defined as shown in Listing 5.6, where Programmer contains a Speak method that shadows the Speak method in the Human class.
To understand what's going on, you must remember how static binding works: The reference variable's type controls method invocation. Now look at the following code:
Dim programmer1 As Programmer = New Programmer Dim human1 As Human Dim message1, message2 As String message1 = programmer1.Speak() '*** calls Programmer.Speak() message2 = human1.Speak() '*** calls Human.Speak()
The reference variable named programmer1 is of type Programmer. Therefore, invoking the Speak method through programmer1 will result in invoking the implementation defined in the Programmer class. Likewise, the reference variable named human1 is of type Human. Therefore, invoking the Speak method through human1 will result in invoking the implementation defined in the Human class. The strange thing about this example is that a single object responds in different ways to a call of Speak depending on the type of reference that is used to access the object. Dynamic binding produces much more intuitive results because the type of object—not the type of reference—determines which method is actually executed.
To make matters worse, it is legal to shadow an overridable method. However, shadowing an overridable method is something you rarely want to do. This possibility is mentioned here only as a warning that sloppy syntax can result in shadowing by mistake. This kind of mistake is likely to lead to trouble. For example, what happens when a base class defines an overridable method, and a derived class author attempts to override it but forgets to use the Overrides keyword? The compiler produces a warning but still compiles your code as if you had used the Shadows keyword:
Public Class Human Public Overridable Function Speak() As String '*** default implementation End Function End Class Public Class Programmer : Inherits Human '*** author forgot to use Overrides keyword Public Function Speak() As String '*** method shadows Human.Speak End Function End Class
Shadowing Overloaded Methods and Properties
Shadowing can become even more complicated when it involves methods and properties that have been overloaded. Recall that the name for a method or property can be overloaded with multiple implementations that differ in terms of their parameter lists. Let's look at an example in which the Human class contains two overloaded methods named Speak, and then the Programmer class inherits from Human and defines Speak so that it shadows one of the inherited methods:
Public Class Human Public Function Speak() As String Return "I am a human" End Function Public Function Speak(ByVal message As String) As String Return "I am a human who says " & message End Function End Class Public Class Programmer : Inherits Human Public Function Speak() As String Return "I am a programmer" End Function End Class
In this example, the definition of the Speak method in the Programmer class will shadow the definition of the Speak method in the Human class with the matching signature. What you might not expect is that the other overloaded definition of Speak within Human is hidden as well. Thus the method with the signature Speak(String) is not part of the Programmer class definition. For this reason, the semantics of shadowing in Visual Basic .NET are sometimes referred to as hide-by-name.
If you try to compile these two class definitions, you will receive another compiler warning. As before, you can suppress this warning by adding the Shadows keyword to the definition of the Speak method in Programmer, as depicted in Listing 5.7.
Listing 5.7 Shadowing an overloaded method
Public Class Human Public Function Speak() As String Return "I am a human" End Function Public Function Speak(ByVal message As String) As String Return "I am a human who says " & message End Function End Class Public Class Programmer : Inherits Human Public Shadows Function Speak() As String Return "I am a programmer" End Function '*** hides Speak(String) from base class End Class
You might ask why the Visual Basic .NET compiler requires you to use the Shadows keyword to suppress the compiler warning in this situation. To understand the motivation behind this requirement, ask yourself the following question: Should the definition for the method with the signature Speak() in the Programmer class hide every definition of Speak in the Human class, or should it just shadow the one with the matching signature? In this case the Shadows keyword indicates that every implementation of Speak in the Human class should be hidden from clients programming against the definition of Programmer.
There's a subtle yet important difference between shadowing a method and hiding a method. In Listing 5.7, the method Speak() is shadowed, whereas the method Speak(String) is hidden. The shadowed method is still accessible to clients through the derived class definition, but the hidden method is not. Take a look at the following client-side code to see the difference. This code creates only one object of type Programmer, yet accesses this same object through two different reference variables:
Dim programmer1 As Programmer = New Programmer Dim human1 As Human Dim message1, message2, message3, message4 As String '*** access object through derived class reference message1 = programmer1.Speak() '*** calls Programmer.Speak() message2 = programmer1.Speak("Hello") '*** error: method doesn't exist '*** access object through base class reference message3 = human1.Speak() '*** calls Human.Speak() message4 = human1.Speak("Hello") '*** calls Human.Speak(String)
As this example reveals, member hiding has a strange side effect. An object created from the Programmer class still provides a definition for Speak(String)—as evidenced by the fact that human1.Speak("Hello") works. However, the Speak(String) method is accessible only to clients that are accessing the object through a reference variable of type Human. As this example involves static binding, a call to Speak() through a reference variable of type Human will use the method definition from Human. Thus hiding doesn't remove a method or property definition from an object; it simply makes a member inaccessible to clients that use reference variables based on the derived class.
You've just seen how Visual Basic .NET allows you to shadow and hide methods using hide-by-name semantics with the Shadows keyword. It also allows you to use the Overloads keyword instead of the Shadows keyword in situations in which you would rather achieve hide-by-signature semantics. With this technique, you can shadow an overloaded method from a base class without hiding other method definitions of the same name. Let's revisit Listing 5.7 and make one minor modification:
Public Class Human Public Function Speak() As String Return "I am a human" End Function Public Function Speak(ByVal message As String) As String Return "I am a human who says " & message End Function End Class Public Class Programmer : Inherits Human Public Overloads Function Speak() As String Return "I am a programmer" End Function '*** inherits Speak(String) from base class End Class
The only change that has been made to this code from Listing 5.7 is that the Overloads keyword has replaced the Shadows keyword in the Programmer class definition of Speak(). This change has the effect of shadowing a method by signature as opposed to hiding it by name. The result is that class Programmer now makes the definition of Speak(String) accessible to clients:
Dim programmer1 As Programmer = New Programmer Dim human1 As Human Dim message1, message2, message3, message4 As String '*** access object through derived class reference message1 = programmer1.Speak() '*** calls Programmer.Speak() message2 = programmer1.Speak("Hello") '*** calls Human.Speak(String) '*** access object through base class reference message3 = human1.Speak() '*** calls Human.Speak() message4 = human1.Speak("Hello") '*** calls Human.Speak(String)
It's now possible to call Speak() and Speak(String) using a reference variable of type Programmer or a reference variable of type Human. One of these method signatures is shadowed, and the other is inherited directly from Human to Programmer. Calls to Speak() are dispatched to either the Human class definition or the Programmer class definition depending on the type of reference variable used. Calls to Speak(String) are always dispatched to the definition in the Human class.
The Overloads keyword should be used on some occasions that do not involve any form of hiding or shadowing. For example, you might want to add a method to a derived class that shares the same name as one or more methods in its base class but doesn't match any of their parameter lists. Suppose you wanted to create a new class that inherits from our running definition of Human (see Listing 5.7). What if you decided to add a third method named Speak that had a signature that was different from the two signatures of Speak inherited from Human? This scenario does not involve either shadowing or hiding, but you can and should use the Overloads keyword:
Class Programmer : Inherits Human '*** inherits Speak() from base class '*** inherits Speak(String) from base class Public Overloads Function Speak(ByVal excited As Boolean) As String If excited Then Return "Oh boy, I am an excited programmer" Else Return "I am a programmer" End If End Function End Class
Now the Programmer class supports three overloaded versions of Speak. Two implementations of Speak are inherited from Human, and a third implementation is added to the Programmer class. Notice that you would get very different results if you do not use the Overloads keyword in the Speak(Boolean) method definition of the Programmer class. If you omit this keyword, the Visual Basic .NET compiler would once again default to using the Shadows keyword. In that case, the Programmer class would contain only one definition of Speak, not three.
Clearly, a design in which members are shadowed and/or hidden has the potential to catch programmers off guard. The shadowing of fields, methods, and properties results in multiple definitions with the same name. Confusion may arise because different types of reference variables produce inconsistent results when accessing the same object.
While most of this discussion has dealt at length with the complexities of shadowing and hiding, you most likely will not have to deal with these topics on a regular basis. In fact, the complexities discussed over the last several pages explain why most designers try their best to avoid designs involving shadowing and hiding. You are well advised to follow suit and avoid the use of shadowing and hiding when you design and implement your own classes.