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
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.
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.
Public NotInheritable Class Class1 '*** class definition End Class
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:
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.
Sub ProcessPerson(obj As Person) obj.Name = "Bob" Dim msg As String
= obj.Speak System.Console.WriteLine(msg) End Sub
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:
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.
'*** 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
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
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.
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.
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 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 Human '*** this class definition will not compile
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
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
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):
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:
Class Class1 Sub Foo() '*** implementation End Sub End Class Class Class2
: Inherits Class1 Sub Bar() '*** implementation End Sub End Class
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
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
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
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
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:
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.
Class Class1 Public Field1 As Integer = 10 End Class Class Class2 : Inherits
Class1 Public Field1 As Integer = 20 End Class
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:
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.
Class Class1 Public Field1 As Integer = 10 End Class Class Class2 : Inherits
Class1 Shadows Public Field1 As Integer = 20 End Class
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:
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.
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
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
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.
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.
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") '***
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).
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.
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)
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:
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 Class1 Sub Foo(i As Integer) '*** method definition End Sub Sub Foo(s
As String) '*** 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.
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
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.
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