Basic Instincts: Using Inheritance in the .NET ...
From the November 2001 issue of MSDN Magazine.
MSDN Magazine
Using Inheritance in the .NET World
Ted Pattison
I
nheritance has long been considered by many as one of the most significant design features of object-oriented programming (OOP). Inheritance was made popular over a decade ago by languages such as C++ and Smalltalk. Since that time, a few new languages (Java, for example) have come along and refined the features and the syntax for using inheritance. Now with the emergence of the .NET Framework, Microsoft has designed a platform from the ground up with support for what is arguably the most elegant form of inheritance to date.
      The more you use the .NET Framework, the more you realize that just about everything in this new platform is based upon inheritance. For example, the type system of the Common Language Runtime (CLR) is heavily reliant upon inheritance. When you begin to use the new Windows® Forms package, you will be required to use inheritance whenever you want to create a new form. When you begin to use ASP .NET, you will be required to use inheritance to create Web pages and Web Services. As a developer building applications or component libraries on the .NET Framework, your familiarity with inheritance is an absolute necessity.
      While inheritance is powerful, it can also require a considerable amount of expertise. You should not underestimate the learning curve. Over the last decade, literally thousands of software projects ran aground because their designers didn't realize the pitfalls and the problems that can occur when inheritance is used incorrectly.
      If you currently program in Visual Basic®, you should consider yourself extremely fortunate. The most recent version of Visual Basic has been updated to include excellent support for the new inheritance features that have been integrated into the .NET platform. If you need to brush up on the principles of inheritance, there are lots of great books and articles that document the best and worst practices. Any programmer using Visual Basic who is willing to invest the time can learn how to avoid the pitfalls of inheritance and make the most of the .NET platform.

Relating Classes through Inheritance

      Inheritance is used to establish a relationship between two different classes. The primary reason for designing with inheritance is that it provides an effective means for reusing code. For example, you can define a set of fields, methods, and properties in one class, and then reuse this code across several other classes. Inheritance is particularly useful in scenarios that require multiple classes that have a lot in common, yet have their own special features.
      Let's look at a simple example to introduce the basic syntax. When you want to explicitly inherit one class from another, you add a line break after the name of the inheriting class followed by the Inherits keyword and the name of the class from which you are inheriting. Examine the three class definitions in Figure 1.
      Note that in the definition for the Programmer class in Figure 1, a colon has been substituted for the line break. The use of a colon to represent a line break is something that's been supported in Visual Basic for years. I often prefer using the colon instead of a line break because I feel it helps to improve readability. However, this is nothing more than a stylistic preference; you can use whichever style you like best.
      In this example, inheritance is used to establish a relationship between the Person class and two other classes, Manager and Programmer. The Person class serves as the base class. It is the class from which the other two classes inherit. The two other classes are known as derived classes.
      The relationship of inheritance implies that these two derived classes automatically inherit the definitions for the Name field and the Speak method from the base class Person. This simple example demonstrates how to use a base class to share member definitions across multiple derived classes. Once you've created a derived class from a base class, you can start adding members to the derived class definition to layer specialized state and behavior on top of the common code defined in the base class.
      Figure 2 shows a common design view of inheritance known as an inheritance hierarchy. The Person class is located at the top of the hierarchy. The Manager and Programmer classes inherit from the Person class and are consequently located directly below it in the inheritance hierarchy.

Figure 2 Inheritance Hierarchy
Figure 2 Inheritance Hierarchy

      There are two other classes at the bottom of the hierarchy named SeniorManager and ManagerTrainee. These two classes have been defined with the Manager class as their base class. As you can see, an inheritance hierarchy can be designed with several levels. The SeniorManager class and the ManagerTrainee class inherit indirectly from the Person class. The main point is that a class inherits from every class that's located above it in an inheritance hierarchy.

Determining When to Use Inheritance

      One of the first places that inexperienced software designers get into trouble is when they try to force an inheritance relationship between two classes in a situation that doesn't make sense. There's an important rule that should always be applied when designing with inheritance. Two classes that are going to be related through inheritance must always be able to pass the "is a" test. In the design shown in Figure 2, you can correctly say that a manager is a type of person. You can also correctly say that a senior manager is a type of manager. As you can see, the purpose of the "is a" test is to ensure that any derived class is being designed to model a more specialized version of whatever entity the base class is modeling.
      You should never try to establish an inheritance relationship between two classes that cannot pass the "is a" test. For example, imagine you saw a novice programmer trying to inherit the Bicycle class from the Wheel class. You should intervene because you cannot correctly say that a bicycle is a type of wheel. You can correctly say that a bicycle has a wheel (or two), but that calls for a different design technique that doesn't involve inheritance.
      One theme should become increasingly clear as you read this column. The use of inheritance increases your responsibilities during both the design and coding phases. If you decide to allow other programmers to use a class you've created as a base class, there are many additional design decisions that you must make. If you make these decisions incorrectly, you can get into a lot of trouble. This is especially true when you need to revise a base class after other programmers have already begun to use it.
      If you don't want the extra responsibilities that go along with authoring and revising a base class, you can define a class in a special way so other programmers can never use it as a base class. Such a class is often called a sealed class. In Visual Basic .NET, you can define a class as sealed by using the NotInheritable keyword.
Public NotInheritable Class Class1 '*** class definition End Class 
      Once you've defined a class using the NotInheritable keyword, another programmer's attempt to inherit from the class will result in a compile-time error. One of the primary benefits of creating a sealed class is that it becomes easier to design and code. You never run into problems because someone used your class in a way you hadn't intended. In general, anytime you create a new class and you don't plan to spend the extra time required to support inheritance, you should define the class using the NotInheritable keyword. As you'll see later, a sealed class can also provide optimizations that improve performance.

Inheritance and Polymorphism

      In addition to allowing component and application designers to achieve code reuse, inheritance is also valuable because it creates an implicit compatibility between a derived class and its base class. This means you can use an object created from a derived class in any situation where an object created from the base class is expected. This allows you to write client-side code in terms of a generic base class and substitute objects created from any class down the inheritance hierarchy. The ability to substitute different type-compatible objects that have slightly different behaviors is known as polymorphism and represents one of the most important concepts in OOP and component-based development.
      Let's look at an example to give you a better idea of how polymorphism is used in a real application design. Suppose you've just created the Person class shown in Figure 1 and several other programmers are using it as a base class to create more specialized versions. You can now start writing generic code against the Person class that is capable of dealing with any object created from any class that inherits directly or indirectly from Person. For example, imagine you define a method that looks like this:
Sub ProcessPerson(obj As Person) obj.Name = "Bob" Dim msg As String
                                = obj.Speak System.Console.WriteLine(msg) End Sub 
      Obviously, when you call ProcessPerson you can legally pass a reference to an instance of the Person class as a parameter value. However, you can also pass a reference to an instance of any class that inherits from Person such as Manager, SeniorManager, or Programmer. The important observation here is that objects created from a derived class are always implicitly compatible with their base class as well as every other class located above their base class in the inheritance hierarchy.

Base Classes in the .NET Framework

      Now that you've seen some of the basic principles and the syntax for using inheritance, it's time to introduce a few important rules that have been imposed by the architects of the CLR. First, you cannot define a class that inherits directly from more than one base class. In other words, the .NET Framework does not allow for multiple inheritance. The second rule is that you cannot define a class that doesn't have a base class. However, this rule can be somewhat confusing at first because you can create a valid class definition in Visual Basic .NET (or in C#) that doesn't explicitly declare a base class. A little more explanation is required to clarify this point.
      When you define a class without explicitly defining a base class, the compiler automatically modifies the class definition to inherit from the system-defined class named System.Object. Once you understand this point, you can see that the following two class definitions both have the same base class:
'*** implicitly inherit from the Object class Public Class Class1 '*** class
                                definition End Class '*** explicitly inherit from the Object class Public Class
                                Class2 : Inherits System.Object '*** class definition End Class 
      The .NET rules of inheritance that I've just outlined can be summarized by saying that every class (with the exception of the Object class) has exactly one base class. It's also important for you to realize that it's not just classes that have base types. Every structure and every enumeration also has a base type. The architects of the CLR designed their type system so that every system-defined type and every user-defined type would fit into the singly rooted inheritance hierarchy shown in Figure 3. The only exception to this rule is interface types, which do not require a base type.

Figure 3 Singly Rooted Inheritance Hierarchy
Figure 3 Singly Rooted Inheritance Hierarchy

      The inheritance hierarchy of the CLR is said to be singly rooted because the system-defined Object class serves as the ultimate base type. Every other class either inherits directly from the Object class or it inherits from another class that inherits (either directly or indirectly) from the Object class. All structure types and enumeration types also inherit indirectly from the Object class. A primary benefit of this inheritance hierarchy design is that all types are compatible with (and implicitly convertible to) the Object class. Any object or value can be safely assigned to a variable, parameter, or field defined in terms of the Object class without the need for an explicit conversion.

Relationship of Class to Base Class

      Remember that objects created from a derived class are type-compatible with their base class. For this reason, all public members that are part of the base class contract are also part of the derived class contract. Furthermore, interfaces that are implemented by the base class are implicitly supported by the derived class as well.
      A derived class inherits the members of its base class. However, the manner in which certain kinds of members are inherited isn't all that intuitive. While the way things work in fields, methods, and properties is fairly straightforward, the manner in which constructors are inherited brings up issues that are more complex and take more time to fully understand.
      Let's start by discussing fields, methods, and properties. Every field, method, and property that is part of the base class definition is inherited into the derived class definition. This means that each object created from a derived type carries with it all the state and behavior that is defined by its base class. However, whether or not code in a derived class can access the members inherited from its base class is a different matter altogether.
      Each base class member is defined with a level of visibility that dictates which other code can access it. A base class member defined with the Private access modifier is not accessible to code inside the derived class or to any client-side code. A base class member defined with the Protected access modifier is accessible to code inside the derived class, but not accessible to clients of either the base class or the derived class. A base class member defined with the Public access modifier is accessible to code inside the derived class and to all client-side code. The code shown in Figure 4 demonstrates legal and illegal accessing of fields of varying levels of visibility.
      Note that every class member is defined with a specific level of visibility whether it has an explicit access modifier or not. A method or property that is declared without an access modifier is implicitly public. A field, on the other hand, must be explicitly defined with an access modifier unless it is declared using either the Dim keyword or the Const keyword. A field that is declared with either Dim or Const, but which does not have an access modifier, is implicitly private.
      There are two other levels of visibility in addition to private, protected, and public. A member that is defined with the Friend access modifier is accessible to all code inside the containing assembly but inaccessible to code in any other assembly. The friend level of visibility does not care whether the accessing code is inside a derived class or not. The fifth and final level of visibility is achieved by using the Protected access modifier together with the Friend access modifier. A member defined as a Protected Friend is visible to all code inside the containing assembly and to derived classes in other assemblies.
      Now it's time for a discussion about how to properly encapsulate base class members when designing with inheritance. As you probably know, encapsulation is the practice of hiding the implementation details of your classes and assemblies from other code. For example, a protected member is encapsulated from client-side code. A private member is encapsulated from client-side code and from code inside derived classes. A Friend member is encapsulated from code that lives outside the containing assembly.
      Imagine you're starting to design a component library that you plan to sell to other companies. In addition, you plan to update this component library from time to time and send your customers the newest version every few months. If your design involves distributing base classes that are intended for other programmers to extend through the use of inheritance, you really have to think carefully about whether you should define these various base class members as private or protected.
      Any member defined as private is fully encapsulated and can be modified or removed without violating the original contract between a base class and any of its derived classes. However, members defined as protected are a significant part of the contract between a base class and its derived classes. Therefore, modifying or removing protected members can break your customer's code.
      If you want to keep customers happy, you must devise a way to maintain and evolve the base classes in your component library without breaking existing code. A decade of experience with inheritance in various languages has shown me that this can be a difficult challenge to meet.
      When authoring base classes, it's critical to start thinking about versioning in the initial design phase. You must determine how easy or hard it will be to modify derived classes if modifications to base classes cause breaking changes. It helps to remember that a common mistake is to forget about encapsulating base class members from derived classes.
      Another very important consideration is whether it makes sense to use inheritance across assembly boundaries. While the plumbing of .NET is more than capable of fusing base class code from one assembly together with a derived class code from a second assembly during object instantiation, you must realize how much harder it becomes to manage versioning.
      I don't mean to suggest that cross-assembly inheritance should never be used. Many experienced designers have found that it can be used effectively. For example, when you use one of the popular components of the .NET Framework such as the Windows Forms package or ASP .NET, you're often required to create a class in a user-defined assembly that inherits from a class in a system-defined assembly. The important point to understand is that the designers at Microsoft who have created these frameworks have thought long and hard about how to maintain and evolve their base classes without introducing breaking changes to your code.
      It is naïve to think about encapsulation only in terms of stand-alone classes. Inheritance and component-based development make the issues much more complex. They also make the mistakes far more costly. Here's a rule of thumb: don't expose base class members that you expect to change to derived classes or to other assemblies when the code that is affected by these changes is beyond your control.

Inheritance and Constructors

      The way in which constructors are inherited isn't as obvious as it is for other kinds of base class members. From the perspective of a client attempting to create an object from the derived class, the derived class definition does not include any of the constructors defined in the base class. Instead, the derived class must provide one or more of its own constructors to support object instantiation. Furthermore, each constructor defined in a derived class must call to a base class constructor before performing any of its own initialization work.
      If a derived class author doesn't explicitly add a constructor, the compiler will typically generate a default constructor (a constructor that takes no parameters) that calls the default constructor of its base class. In a situation in which the base class does not have a public or protected default constructor, then a derived class definition without a constructor is invalid and will not compile.
      The only time a derived class author can get away without explicitly adding a constructor is when the base class provides an accessible default constructor. As it turns out, the Object class contains a public default constructor. This means you don't have to explicitly add a constructor to a class that inherits from the Object class. Likewise, you don't have to explicitly add a constructor to a class that inherits from another class with an accessible default constructor. However, this isn't always the case. There are times when you must inherit from a class that doesn't contain a default constructor. For example, look at the following definition of the Person class.
Public Class Person Protected m_Name As String Sub New(Name As String) '***
                                implicit call to default constructor of Object m_Name = Name End Sub End Class 
      Since this definition for the Person class contains a single parameterized constructor, the compiler doesn't automatically add a default constructor. If you try to create a class that inherits from this class, and you do not provide an explicit constructor, your code will not compile.
Class Programmer : Inherits Human '*** this class definition will not compile
                                End Class 
      Since the definition for the Person class doesn't contain a default constructor, the compiler cannot automatically generate a default constructor for the Programmer class that calls the default constructor of the Person class. The only way to make the Programmer class compile is to add a constructor that explicitly calls to an accessible constructor defined in the Person class:
Class Programmer : Inherits Person Sub New(Name As String) MyBase.New(Name)
                                '*** call to base class constructor '*** Programmer-specific initialization goes
                                here End Sub End Class 
      As you can see from the previous snippet, you can make an explicit call to a base class constructor within a derived class constructor using the syntax MyBase.New and passing the appropriate list of parameters. It's important to note that when you explicitly call a base class constructor from a derived class constructor, you can only do it once and it must be the first thing you do.
      Take a moment and consider the sequence in which the constructors are executed during object instantiation. Let's examine the scenario in which a client creates an object from the Programmer class using the New operator. When the client calls New, a constructor in the Programmer class begins to execute. However, before this constructor can do anything interesting, it must call to a constructor in the Person class. The constructor in the Person class faces the same constraints. Before it can do anything interesting, it must call to the default constructor of the Object class.
      The important observation is that constructors execute in a chain starting with the least-derived class and ending with the most-derived class. The constructor of the Object class always runs first. When the constructor for the Object class completes, the constructor for the Person class is executed. Finally, when all the other constructors have run, the constructor for the most-derived class (in this case, Programmer) is executed. After the entire chain of constructors has finished executing, control is returned to the client who started the sequence by calling the New operator.

Static versus Dynamic Binding

      If you are designing and writing class definitions, it's critical to understand the difference between static binding and dynamic binding. These are the two ways a client can interact with an object in the CLR to access a member such as a field, method, or property. Static binding is usually more straightforward and results in better performance. Dynamic binding, on the other hand, yields more flexibility and provides a critical mechanism that allows a derived class author to replace behavior defined in a base class.
      Before I discuss static binding versus dynamic binding, let's review an important principle related to programming with objects in the CLR: you should always distinguish between the type of object you're programming against the type of reference you're using to interact with that object. Suppose you're dealing with a class (Class2) that derives from another class (Class1):
Class Class1 Sub Foo() '*** implementation End Sub End Class Class Class2
                                : Inherits Class1 Sub Bar() '*** implementation End Sub End Class 
      If you create an object using Class2, you can interact with that object using a reference variable of type Class2 or a reference variable of type Class1. Here's an example of some code that illustrates this point:
Dim refA As Class2 = New Class2 '*** access object through derived class
                                reference refA.Bar '*** calls Class2.Bar refA.Foo '*** calls Class1.Foo '*** access
                                object through base class reference Dim refB As Class1 = refA refB.Foo '*** calls
                                Class1.Foo 
As you can see, a single object of type Class2 can be accessed using two different types of reference variables. One reference variable is based on Class2 and a second reference variable is based on Class1. The important point for the purposes of this discussion is that an object and the reference variables used to access it are often based on different types. The only requirement is that the reference variable type is compatible with the object type. As mentioned earlier, a base class is always compatible with any object created from a class that derives from it either directly or indirectly.
      As it turns out, static binding is the default binding type used by Visual Basic .NET and the CLR. In fact, the client-side code shown in the last example will result in static binding. At this point, it's worth spending some time examining the mechanics of static binding. This will give you an important level of understanding when I compare static binding to dynamic binding later on.
      Static binding is based on the type of the reference variable being used and not on the type of object being used. For example, if you attempt to access an object created from Class2 through a reference variable of type Class1, the type information from the definition of Class1 is used to bind the client to the correct member definition in Class2.
      With static binding the decision about where to locate the definition for the member being accessed is made at compile time. This is different from client-object interaction using dynamic binding, in which the decision about where to locate the member definition is not made until runtime. With static binding, the compiler can get more work out of the way at compile time. With dynamic binding, this work needs to be done while a program is running. For this reason, static binding has a measurable performance advantage over dynamic binding.
      Since static binding is more straightforward and faster than dynamic binding, the architects of the CLR and the designers of languages such as Visual Basic .NET and C# use static binding by default. Dynamic binding, on the other hand, is never used by default. As you'll see later, it requires effort on the part of class authors. To implement dynamic binding, the class authors must use language-specific keywords.

Shadowing and Hiding

      While most uses of static binding are pretty straightforward, this is not always the case. There are certain situations in which static binding can become complex and not very intuitive. In particular, static binding gets very tricky when a base class and a derived class have one or more member definitions with the same name. Let's look at an example that demonstrates this point. Suppose there is a base class and a derived class with these definitions:
Class Class1 Public Field1 As Integer = 10 End Class Class Class2 : Inherits
                                Class1 Public Field1 As Integer = 20 End Class 
      Note that both class definitions contain an integer field named Field1. When a member in a derived class is defined in this manner with same name as a member in its base class, it's called shadowing. That is, the Class2 definition of Field1 shadows the Class1 definition of Field1.
      The Visual Basic .NET compiler produces a compile-time warning when you shadow a member in your base class or any other base class higher up in the inheritance hierarchy. The compiler issues this warning in case you have used shadowing accidentally. However, if you deliberately want to shadow a field from the base class, you can suppress the compiler warning by making your intentions explicit with the Shadows keyword:
Class Class1 Public Field1 As Integer = 10 End Class Class Class2 : Inherits
                                Class1 Shadows Public Field1 As Integer = 20 End Class 
      In a few rare situations, an experienced class designer may decide to use shadowing. However, in most cases the use of shadowing creates ambiguities that make it easy for a client-side programmer to get into trouble. The problem is that static binding produces inconsistent results when an object with shadowed members is accessed by different types of reference variables.
      Let's take a quick look at an example of how shadowing members in a base class has the potential to create confusion. Imagine you're writing code in which you're going to create an object from Class2. Remember from the previous code sample that Class2 contains a field named Field1 that shadows another field named Field1 in Class1.
      The first thing that's confusing is that an object created from Class2 has two separate instances of Field1. However, it's not immediately clear which of these two instances the client is going to access when programming against the member named Field1. In order to understand what's going on, you must remember how static binding works. The type of the reference variable determines whether the client accesses the Class1 definition of Field1 versus the Class2 definition of Field1.
      Look at the following code:
Dim i, j As Integer Dim refA As Class2 = New Class2 Dim refB As Class1 =
                                refA '*** access object through derived class reference i = refA.Field1 '*** i =
                                20 '*** access object through base class reference j = refB.Field1 '*** j = 10 
Since the reference variable named refA is of type Class2, it can be used to access the instance of Field1 defined in Class2. Likewise, the reference variable named refB is of type Class1 and, therefore, can be used to access the instance of Field1 defined in Class1.
      If you think this example of shadowing field definitions is confusing, you're not alone. Most programmers writing client-side code don't expect to find multiple instances of a field with the same name, and they can easily be caught off guard when accessing an object through different types of references. What you've seen so far should motivate you to avoid shadowing when you're designing classes.
      Shadowing can get even more complicated when it involves methods and properties. This is due to the fact that the name for a method or a property can be overloaded with multiple definitions that differ in terms of their parameter lists. Let's look at an example in which a base class contains two overloaded methods named Foo and a derived class contains the definition for a method with the same name (see Figure 5).
      In this scenario, the definition of the Foo method in Class2 will shadow the definition of the Foo method in Class2 with a matching signature. What you might not expect is that the other overloaded definition of the Foo method from Class1 is hidden from the Class2 definition. If you try to compile the two class definitions shown in Figure 5, you will receive another compiler warning. You can suppress this compiler warning by adding the Shadows keyword to the definition of the Foo method in Class2 (see Figure 6).
      You might ask yourself why the Visual Basic .NET compiler requires you to use the Shadows keyword in this situation to suppress the compiler warning. In order to understand the extent of the motivations behind this requirement, you must ask yourself this question: should the definition for the method with the signature Foo(Integer) in Class2 hide every definition of Foo in Class1, or should it just shadow the one with the matching signature? In this case, the Shadows keyword is used to indicate that all Class1 method definitions of Foo are to be shadowed or hidden from the definition of Class2.
      There's a subtle yet important difference between shadowing a method and hiding a method. In the previous example, the method Foo(Integer) is shadowed, while the method Foo(String) is hidden. The shadowed method is still accessible to clients through the derived class definition, while the hidden method is not. The following client-side code shows the difference.
Dim refA As Class2 = New Class2 Dim refB As Class1 = refA '*** access object
                                through derived class reference refA.Foo(7) '*** calls Class2.Foo(Integer) refA.Foo("hi")
                                '*** compile-time error: method does not exist '*** access object through base class
                                reference refB.Foo(14) '*** calls Class1.Foo(Integer) refB.Foo("ho") '***
                                calls Class1.Foo(String) 
      As you can see from this example, member hiding has a strange side effect. An object created from Class2 still contains a definition for Foo(String). However, it is only accessible to clients who are accessing the object through a reference variable of type Class1. Since this example involves static binding, a call to Foo(String) through a reference variable of type Class1 will use the method definition from Class1. You should note that hiding doesn't remove a method or property definition from an object. It simply makes a member inaccessible to clients who are using 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. However, Visual Basic .NET also allows you to use the Overloads keyword instead of the Shadows keyword in situations in which you would rather achieve shadow-by-signature semantics. This allows you to shadow an overloaded method from a base class without hiding other method definitions of the same name. Figure 7 revisits the example shown in Figure 6, adding one minor modification.
      The only change shown in Figure 7 (from the code in Figure 6) is that the Shadows keyword in the Class2 definition of Foo(Integer) has been replaced with the Overloads keyword. This has the effect of shadowing a method by signature as opposed to hiding it by name. Unlike the previous example using the Shadows keyword, this definition of Class2 inherits the definition of Foo(String).
Dim refA As Class2 = New Class2 Dim refB As Class1 = refA '*** access object
                                through derived class reference refA.Foo(7) '*** calls Class2.Foo(Integer) refA.Foo("hi")
                                '*** calls Class1.Foo(String) '*** access object through base class reference refB.Foo(14)
                                '*** calls Class1.Foo(Integer) refB.Foo("ho") '*** calls Class1.Foo(String)
                            
      As you can see, it's possible to call Foo(Integer) and Foo(String) through either a reference variable of type Class2 or a reference variable of type Class1. One of these method signatures is shadowed, while the other is directly inherited from Class1 to Class2. Calls to Foo(Integer) are dispatched to either the Class2 definition or the Class1 definition, depending on the type of reference variable being used. Calls to Foo(String) are always dispatched to the definition in Class1.
      It's important to note that the Class2 definition that uses the Overloads keyword is different from the earlier definition that used the Shadows keyword. The newer Class2 definition does not hide the Class1 definition of Foo(String). Therefore, a client can call Foo(String) when accessing an object through a reference variable of type Class2.
      There are times when the Overloads keyword should be used without 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. For example, let's say you wanted to create a new class that inherits from the following definition of Class1, containing overloaded definitions of Foo:
Class Class1 Sub Foo(i As Integer) '*** method definition End Sub Sub Foo(s
                                As String) '*** method definition End Sub End Class 
What if you wanted to add a third method named Foo that had a signature that was different from the two signatures of Foo just shown? This scenario involves no shadowing or hiding. However, you should use the Overloads keyword:
Class Class2 : Inherits Class1 '*** Sub Foo(Integer) is inherited from Class1
                                '*** Sub Foo(String) is inherited from Class1 Overloads Sub Foo(d As Double) '***
                                method definition End Sub End Class 
      If you omit the Overloads keyword from the method definition in Class2, the Visual Basic .NET compiler will generate a warning, telling you a method of the same name is already defined in the base class. Even though your code will still compile and produce the same results without the Overloads keyword, the Visual Basic .NET compiler is asking you to be more explicit about your intentions. The designers of the Visual Basic .NET language want you to tell the compiler whether you just want to add a new overloaded method or you want to hide the method by name. To be precise, you must commit to either the Overloads keyword or the Shadows keyword in order to suppress the compiler warning.
      At this point, it should be clear that a design in which members are shadowed and/or hidden has the potential to catch programmers off guard. When you define a method with the same name and the same signature as another method in its base class, the difference between using the Overloads keyword and the Shadows keyword is quite subtle. The Shadows keyword results in hide-by-name semantics while the Overloads keyword results in shadow-by-signature semantics. The usage of either keyword has the potential to cause confusion due to the fact that different types of reference variables can result in inconsistent behavior within a single object.
      You could argue that using the Shadows keyword with method and properties is potentially more confusing to client-side programmers than using the Overloads keyword. This is because the use of the Shadows keyword can produce the effect of hiding members. Member hiding results in a derived class that doesn't support one or more members that are supported by its base class. Your intuition tells you that a derived class should contain a superset of the members found in its base class. However, the use of the Shadows keyword can result in designs that are counterintuitive.
      While most of this discussion has dealt with the complexities of shadowing and hiding, you should be glad to know that you will not have to deal with these topics on a regular basis. In fact, most designers do whatever they can to avoid shadowing and hiding.

Conclusion

      Most of the work for static binding occurs at compile time, which improves performance. Static binding is based on the type of the reference variable being used and not on the type of the object. In addition, static binding can produce confusing and inconsistent results when a derived class shadows or hides members from its base class. Once you understand these three principles, it's much easier to appreciate how and why dynamic binding works the way it does.
      In the next installment of this column, I will discuss the use of dynamic binding and how you can design base classes with overridable methods in Visual Basic .NET.

Send questions and comments to Ted at basic@microsoft.com.
Ted Pattison is an instructor and researcher at DevelopMentor (http://www.develop.com), where he co-manages the Visual Basic curriculum. The second edition of Ted's book, Programming Distributed Applications with COM and Microsoft Visual Basic 6.0, was published by Microsoft Press in June 2000.

Page view tracker