Chapter 4
Custom Objects
What's in this chapter?
Inheritance
The MyBase keyword
Event Handling in Sub Classes
Creating Abstract Base Class
Interfaces
Abstraction
Encapsulation
Polymorphism
Visual Basic is a fully object-oriented language. Chapter 3 covered the basics of creating classes and objects, including the creation of methods, properties, events, operators, and instance variables. You have seen the basic building blocks for abstraction, encapsulation, and polymorphism—concepts discussed in more detail at the end of this chapter. The final major techniques you need to understand are inheritance and the use of multiple interfaces.
Inheritance is the idea that you can create a class that reuses methods, properties, events, and variables from another class. You can create a class with some basic functionality, and then use that class as a base from which to create other, more detailed, classes. All these derived classes will have the same common functionality as that base class, along with new, enhanced, or even completely changed functionality.
This chapter covers the syntax that supports inheritance within Visual Basic. This includes creating the base classes from which other classes can be derived, as well as creating those derived classes.
Visual Basic also supports a related concept: multiple interfaces. As shown in Chapter 3, all objects have a native or default interface, which is defined by the public methods, properties, and events declared in the class. These additional interfaces define alternative ways in which your object can be accessed by providing clearly defined sets of methods, properties, and events. Like the native interface, these secondary interfaces define how the client code can interact with your object, essentially providing a “contract” that enables the client to know exactly what methods, properties, and events the object will provide. When you write code to interact with an object, you can choose which of the interfaces you want to use; basically, you are choosing how you want to view or interact with that object.
This chapter uses relatively basic code examples so that you can focus on the technical and syntactic issues surrounding inheritance and multiple interfaces. The last part of this chapter revisits these concepts using a more sophisticated set of code as you continue to explore object-oriented programming and how to apply inheritance and multiple interfaces in a practical manner.
Successfully applying Visual Basic's object-oriented capabilities requires an understanding of object-oriented programming. This chapter applies Visual Basic's object-oriented syntax, showing how it enables you to build object-oriented applications. It also describes in detail the four major object-oriented concepts: abstraction, encapsulation, polymorphism, and inheritance. By the end of this chapter, you will understand how to apply these concepts in your design and development efforts to create effective object-oriented applications.
Each box in this diagram represents a class; in this case, you have Person, Employee, and Customer classes. The line from Employee back up to Person, terminating in a triangle, indicates that Employee is derived from, or inherits from, Person. The same is true for the Customer class.
Inheritance is one of the most powerful object-oriented features a language can support. At the same time, inheritance is one of the most dangerous and misused object-oriented features.
Properly used, inheritance enables you to increase the maintainability, readability, and reusability of your application by offering you a clear and concise way to reuse code, via both interface and implementation. Improperly used, inheritance creates applications that are very fragile, whereby a change to a class can cause the entire application to break or require changes.
Inheritance enables you to implement an is-a relationship. In other words, it enables you to implement a new class that “is a” more specific type of its base class. Properly used, inheritance enables you to create child classes that are actually the same as the base class.
For example, you know that a jet is an airplane. A jet is in part defined by its engine, though that is not its primary identity. Proper use of inheritance enables you to create an Airplane base class from which you can derive a Jet class. You would not subclass Jet from an Engine class, as jet isn't an engine—although it has an Engine.
This is the challenge. Inheritance is not just a mechanism for code reuse, but a mechanism to create classes that flow naturally from another class. If you use it anywhere you want code reuse, you will end up with a real mess on your hands. Using inheritance simply because you see a common interface during design isn't best practice. Instead if you just need a common set of methods and properties with a custom implementation for those methods and properties, you should use an interface definition. The creation of custom interface definitions is discussed later in this chapter.
It's necessary to understand some basic terms associated with inheritance—and there are a lot of terms, partly because there are often several ways to say the same thing. The various terms are all used quite frequently and interchangeably.
Inheritance, for instance, is also sometimes referred to as generalization, because the class from which you are inheriting your behavior is virtually always a more general form of your new class. A person is more general than an employee, for instance.
The inheritance relationship is also referred to as an is-a relationship. When you create a Customer class that inherits from a Person class, that customer is a person. The employee is a person as well. Thus, you have the is-a relationship. As shown later in the chapter, multiple interfaces can be used to implement something similar to the is-a relationship, the act-as relationship.
When you create a class using inheritance, it inherits behaviors and data from an existing class. That existing class is called the base class. It is also often referred to as a superclass or a parent class.
The class you create using inheritance is based on the parent class. It is called a subclass. Sometimes it is also called a child class or a derived class. In fact, the process of inheriting from a base class by a subclass is referred to as deriving. You are deriving a new class from the base class. This process is also called subclassing.
The way you use inheritance in the design of a framework is somewhat different from how you use inheritance in the design of an actual application. In this context, the word framework is being used to refer to a set of classes that provide base functionality that isn't specific to an application, but rather may be used across a number of applications within the organization, or perhaps even beyond the organization. The .NET Framework base class library is an example of a very broad framework you use when building your applications.
While inheritance is powerful, it is really geared for implementing the is-a relationship. Sometimes you will have objects that need a common interface, even though they are not really a specific case of some base class that provides that interface. Often a custom interface is a better alternative than inheritance.
For starters, you can only inherit a single common interface. However, a class can implement multiple different interfaces. Multiple interfaces can be viewed as another way to implement the is-a relationship. However, it is often better to view inheritance as an is-a relationship and to view multiple interfaces as a way of implementing an act-as relationship.
When a class implements an abstract interface such as IPrintableObject, you are not really saying that your class is a printable object, you are saying that it can “act as” a printable object. An Employee is a Person, but at the same time it can act as a printable object. This is illustrated in .
The drawback to this approach is that you have no inherited implementation when you use an abstract interface. While not as automatic or easy as inheritance, it is possible to reuse implementation code with a bit of extra work. But first you will look at implementing inheritance.
To implement a class using inheritance, you start with an existing class from which you will derive your new subclass. This existing class, or base class, may be part of the .NET system class library framework, it may be part of some other application or .NET assembly, or you may create it as part of your application.
Once you have a base class, you can then implement one or more subclasses based on that base class. Each of your subclasses automatically inherits all of the methods, properties, and events of that base class—including the implementation behind each method, property, and event. Your subclass can also add new methods, properties, and events of its own, extending the original interface with new functionality. In addition, a subclass can replace the methods and properties of the base class with its own new implementation—effectively overriding the original behavior and replacing it with new behaviors.
Essentially, inheritance is a way of combining functionality from an existing class into your new subclass. Inheritance also defines rules for how these methods, properties, and events can be merged, including control over how they can be changed or replaced, and how the subclass can add new methods, properties, and events of its own. This is what you will learn in the following sections—what these rules are and what syntax you use in Visual Basic to make it all work.
Virtually any class you create can act as a base class from which other classes can be derived. In fact, unless you specifically indicate in the code that your class cannot be a base class, you can derive from it (you will come back to this later).
Create a new Windows Forms Application project in Visual Basic by selecting File ⇒ New Project and selecting Windows Forms Application. Then add a class to the project using the Project ⇒ Add Class menu option and name it Person.vb.
At this point, you technically have a base class, as it is possible to inherit from this class even though it doesn't do or contain anything. You can now add methods, properties, and events to this class. For instance, add the following code to the Person.vb class:
Public Class Person Public Property Name As String Public Property BirthDate As Date End Class
To implement inheritance, you need to add a new class to your project. Use the Project ⇒ Add Class menu option and add a new class named Employee.vb as follows:
Public Class Employee Inherits Person Public Property HireDate As Date Public Property Salary As Double End Class
This is a regular standalone class with no explicit inheritance. It can be represented by the class diagram shown in . The diagram in also illustrates the fact that the Employee class is a subclass of Person.
In this representation of the class as it is presented from Visual Studio, the top box represents the Person class. In the top section of this box is the name of the class and a specification that it is a class. The section below it contains a list of the properties exposed by the class, both marked as Public. If the class had methods or events, then they would be displayed in their own sections in the diagram.
In the box below, you again see the Employee class name and properties. It turns out that behind the scenes, this class inherits some capabilities from System.Object. Chapter 3 made clear that every class in the entire .NET platform ultimately inherits from System.Object either implicitly or explicitly.
While having an Employee object with a hire date and salary is useful, it should also have Name and BirthDate properties, just as you implemented in the Person class. Without inheritance, you would probably just copy and paste the code from Person directly into the new Employee class, but with inheritance, you get to directly reuse the code from the Person class.
The Inherits keyword indicates that a class should derive from an existing class, inheriting the interface and behavior from that class. You can inherit from almost any class in your project, from the .NET system class library, or from other assemblies. It is also possible to prevent inheritance, which is covered later in the chapter. When using the Inherits keyword to inherit from classes outside the current project, you need to either specify the namespace that contains that class or place an Imports statement at the top of the class to import that namespace for your use.
The line running from Employee back up to Person ends in an open triangle, which is the symbol for inheritance when using the Class Designer in Visual Studio. It is this line that indicates that the Employee class includes all the functionality, as well as the interface, of Person.
This means that an object created based on the Employee class has not only the methods HireDate and Salary, but also Name and BirthDate. To test this, bring up the designer for and add the controls shown in to the form.
Control Type | Name Property Value | Text Property Value |
TextBox | TextBoxName | <blank> |
TextBox | TextBoxDOB | <blank> |
TextBox | TextBoxHireDate | <blank> |
TextBox | TextBoxSalary | <blank> |
button | ButtonOK | OK |
You should also add some labels to make the form readable. The Form Designer should now look something like .
Double-click the OK button to bring up the code window and enter the following into the Form1.vb code file:
Private Sub ButtonOK_Click(sender As Object, e As EventArgs) Handles ButtonOK.Click Dim emp As New Employee() With emp .Name = "Ben" .BirthDate = #1/1/1975# .HireDate = #1/1/2000# .Salary = 30000 TextBoxName.Text = .Name TextBoxDOB.Text = Format(.BirthDate, "Short date") TextBoxHireDate.Text = Format(.HireDate, "Short date") TextBoxSalary.Text = Format(.Salary, "$0.00") End With End Sub
Even though Employee does not directly implement the Name or BirthDate methods, they are available for use through inheritance. When you run this application and click the button, your controls are populated with the values from the Employee object.
When the code in Form1 invokes the Name property on the Employee object, the code from the Person class is executed, as the Employee class has no such property. However, when the HireDate property is invoked on the Employee object, the code from the Employee class is executed.
From the form's perspective, it doesn't matter whether a method is implemented in the Employee class or the Person class; they are all simply methods of the Employee object. In addition, because the code in these classes is merged to create the Employee object, there is no performance difference between calling a method implemented by the Employee class or calling a method implemented by the Person class.
Although your Employee class automatically gains the Name and BirthDate methods through inheritance, it also has methods of its own—HireDate and Salary. This shows how you have extended the base Person interface by adding methods and properties to the Employee subclass.
You can add new properties, methods, and events to the Employee class, and they will be part of any object created based on Employee. This has no impact on the Person class whatsoever, only on the Employee class and Employee objects.
You can even extend the functionality of the base class by adding methods to the subclass that have the same name as methods or properties in the base class, as long as those methods or properties have different parameter lists. You are effectively overloading the existing methods from the base class. It is essentially the same thing as overloading regular methods, as discussed in Chapter 3.
For example, your Person class is currently providing your implementation for the Name property. Employees may have other names you also want to store, perhaps an informal name and a formal name in addition to their regular name. One way to accommodate this requirement is to change the Person class itself to include an overloaded Name property that supports this new functionality. However, you are really only trying to enhance the Employee class, not the more general Person class, so what you want is a way to add an overloaded method to the Employee class itself, even though you are overloading a method from its base class.
You can overload a method from a base class by using the Overloads keyword. The concept is the same as described in Chapter 3, but in this case an extra keyword is involved. To overload the Name property, for instance, you can add a new property to the Employee class. First, though, define an enumerated type using the Enum keyword. This Enum will list the different types of name you want to store. Add this Enum to the Employee.vb file, before the declaration of the class itself:
Public Enum NameTypes NickName = 1 FullName = 2 End Enum
You can then add an overloaded Name property to the Employee class itself as follows:
Public Class Employee Inherits Person Public Property HireDate As Date Public Property Salary As Double Private mNames As New Generic.Dictionary(Of NameTypes, String) Public Overloads Property Name(ByVal type As NameTypes) As String Get Return mNames(type) End Get Set(ByVal value As String) If mNames.ContainsKey(type) Then mNames.Item(type) = value Else mNames.Add(type, value) End If End Set End Property
This Name property is actually a property array, which enables you to store multiple values via the same property. In this case, you are storing the values in a Generic.Dictionary(Of K, V)object, which is indexed by using the Enum value you just defined. Chapter 7 discusses generics in detail. For now, you can view this generic Dictionary just like any collection object that stores key/value data.
Though this method has the same name as the method in the base class, the fact that it accepts a different parameter list enables you to use overloading to implement it here. The original Name property, as implemented in the Person class, remains intact and valid, but now you have added a new variation with this second Name property, as shown in .
The diagram clearly indicates that the Name method in the Person class and the Name method in the Employee class both exist. If you hover over each Name property in the Class Designer, you will see a tooltip showing the method signatures, making it clear that each one has a different signature.
You can now change Form1 to make use of this new version of the Name property. First, add a couple of new TextBox controls and associated labels. The TextBox controls should be named TexboxFullName and TextBoxNickName. Double-click the form's OK button to bring up the code window and overwrite the code with the following to work with the overloaded version of the Name property:
Private Sub ButtonOK_Click(sender As Object, e As EventArgs) Handles ButtonOK.Click Dim emp As New Employee() With emp .Name = "Ben" .Name(NameTypes.FullName) = "Benjamin Franklin" .Name(NameTypes.NickName) = "Benjie" .BirthDate = #1/1/1960# .HireDate = #1/1/1980# .Salary = 30000 TextBoxName.Text = .Name TextBoxDOB.Text = Format(.BirthDate, "Short date") TextBoxHireDate.Text = Format(.HireDate, "Short date") TextBoxSalary.Text = Format(.Salary, "$0.00") TextBoxFullName.Text = .Name(NameTypes.FullName) TextBoxNickName.Text = .Name(NameTypes.NickName) End With End Sub
The code still interacts with the original Name property as implemented in the Person class, but you are now also invoking the overloaded version of the property implemented in the Employee class.
So far, you have seen how to implement a base class and then use it to create a subclass. You also extended the interface by adding methods, and you explored how to use overloading to add methods that have the same name as methods in the base class but with different parameters.
However, sometimes you may want to not only extend the original functionality, but also actually change or entirely replace the functionality of the base class. Instead of leaving the existing functionality and just adding new methods or overloaded versions of those methods, you might want to override the existing functionality with your own.
You can do exactly that, if the base class allows it. When a base method is marked as overridable, then you can substitute your own implementation of a base class method—meaning your new implementation will be used instead of the original.
To do this the base class must be coded specifically to allow this to occur, by using the Overridable keyword. This is important, as you may not always want to allow a subclass to entirely change the behavior of the methods in your base class. However, if you do wish to allow the author of a subclass to replace your implementation, you can do so by adding the Overridable keyword to your method declaration.
Returning to the employee example, you may not like the implementation of the BirthDate method as it stands in the Person class. Suppose, for instance, that you can't employ anyone younger than 16 years of age, so any birth date within 16 years in the past is invalid for an employee.
To implement this business rule, you need to change the way the BirthDate property is implemented. While you could make this change directly in the Person class, that would not be ideal. It is perfectly acceptable to have a person under age 16, just not an employee.
Open the code window for the Person class and change the BirthDate property to include the Overridable keyword:
Public Overridable Property BirthDate As Date
This change allows any class that inherits from Person to entirely replace the implementation of the BirthDate property with a new implementation.
If the subclass does not override this method, the method works just like a regular method and is still included as part of the subclass's interface. Putting the Overridable keyword on a method simply allows a subclass to override the method.
In a subclass, you override a method by implementing a method with the same name and parameter list as the base class, and then you use the Overrides keyword to indicate that you are overriding that method.
This is different from overloading, because when you overload a method you are adding a new method with the same name but a different parameter list. When you override a method, you are actually replacing the original method with a new implementation.
Without the Overrides keyword, you will receive a compilation error when you implement a method with the same name as one from the base class. Open the code window for the Employee class and add a new BirthDate property as follows:
Private mBirthDate As Date Public Overrides Property BirthDate() As Date Get Return mBirthDate End Get Set(ByVal value As Date) If DateDiff(DateInterval.Year, Value, Now) >= 16 Then mBirthDate = value Else Throw New ArgumentException( _ "An employee must be at least 16 years old.") End If End Set End Property
Because you are implementing your own version of the property, you have to declare a variable to store that value within the Employee class.
Notice how you have enhanced the functionality in the Set block. It now raises an error if the new birth-date value would cause the employee to be less than 16 years of age. With this code, you have now entirely replaced the original BirthDate implementation with a new one that enforces your business rule.
If you run your application and click the button on the form, then everything should work as it did before, because the birth date you are supplying conforms to your new business rule. Now change the code in your form to use an invalid birth date like “1/1/2012.”
When you run the application (from within Visual Studio) and click the button, you receive an exception indicating that the birth date is invalid. This proves that you are now using the implementation of the BirthDate method from the Employee class, rather than the one from the Person class. Change the date value in the form back to a valid value so that your application runs properly.
You have just seen how you can entirely replace the functionality of a method in the base class by overriding it in your subclass. However, this can be somewhat extreme; sometimes it's preferable to override methods so that you extend the base functionality, rather than replace it.
To do this, you need to override the method using the Overrides keyword as you just did, but within your new implementation you can still invoke the original implementation of the method.
To invoke methods from the base class, you can use the MyBase keyword. This keyword is available within any class, and it exposes all the methods of the base class for your use.
This means that within the BirthDate implementation in Employee, you can invoke the BirthDate implementation in the base Person class. This is ideal, as it means that you can leverage any existing functionality provided by Person while still enforcing your Employee-specific business rules.
To do this, you modify the code in the Employee implementation of BirthDate. First, remove the declaration of mBirthDate from the Employee class. You won't need this variable any longer because the Person implementation will keep track of the value on your behalf. Then, change the BirthDate implementation in the Employee class as with the following:
Public Overrides Property BirthDate() As Date Get Return MyBase.BirthDate End Get Set(ByVal value As Date) If DateDiff(DateInterval.Year, Value, Now) >= 16 Then MyBase.BirthDate = value Else Throw New ArgumentException( _ "An employee must be at least 16 years old.") End If End Set End Property
Run your application, and you will see that it works just fine and returns the error, even though the Employee class no longer contains any code to actually keep track of the birth-date value. You have merged the BirthDate implementation from Person right into your enhanced implementation in Employee.
The MyBase keyword is covered in more detail later in the chapter.
The BirthDate property is an example of a virtual method. Virtual methods are methods or properties in a base class that can be overridden by subclasses.
With a nonvirtual method, only one implementation matches any given method signature, so there's no ambiguity about which specific method implementation will be invoked. With virtual methods, however, there may be several implementations of the same method, with the same method signature, so you need to understand the rules that govern which specific implementation of that method will be called.
When working with virtual methods, keep in mind that the data type of the original object instance is used to determine the implementation of the method to call, rather than the type of the variable that refers to the object.
Looking at the code in your form, you are declaring an object variable of type Employee, and then creating an Employee object that you can reference via that object.
However, when you call the BirthDate property, you know that you are invoking the implementation contained in the Employee class, which makes sense because you know that you are using a variable of type Employee to refer to an object of type Employee.
Because your methods are virtual methods, you can experiment with a more interesting scenario. For instance, suppose that you change the code in your form to interact directly with an object of type Person instead of one of type Employee as follows (code file: Form1.vb):
Private Sub ButtonOK_Click(sender As Object, e As EventArgs) Handles ButtonOK.Click Dim person As New Person() With person .Name = "Ben" .BirthDate = #1/1/1975# TextBoxName.Text = .Name TextBoxDOB.Text = Format(.BirthDate, "Short date") End With End Sub
You can't call the methods implemented by the Employee class, because they do not exist as part of a Person object. However, you can see that the Name and BirthDate properties continue to function. When you run the application now, it will work just fine. You can even change the birth-date value to something that would be invalid for Employee, like “1/1/2012.” It works just fine, because the BirthDate method you are invoking is the original version from the Person class.
Thus you have the ability to have either a variable and an object of type Employee or a variable and an object of type Person. However, because Employee is derived from Person, you can do something a bit more interesting. You can use a variable of type Person to hold a reference to an Employee object. For example, you can change the code in Form1.vb as follows:
Private Sub ButtonOK_Click(sender As Object, e As EventArgs) Handles ButtonOK.Click Dim emp As Person emp = New Employee() With emp .Name = "Ben" .BirthDate = #1/1/1975# txtName.Text = .Name txtBirthDate.Text = Format(.BirthDate, "Short date") End With End Sub
What you are doing now is declaring your variable to be of type Person, but the object itself is an instance of the Employee class. You have done something a bit complex here, as the data type of the variable is not the same as the data type of the object itself. Remember that a variable of a base-class type can always hold a reference to an object of any subclass.
This technique is very useful when creating generic routines. It makes use of an object-oriented concept called polymorphism. This technique enables you to create a more general routine that populates your form for any object of type Person. Add the following code to Form1.vb:
Private Sub DisplayPerson(ByVal thePerson As Person) With thePerson TextBoxName.Text = .Name TextBoxDOB.Text = Format(.BirthDate, "Short date") End With End Sub
Now you can change the code behind the button to make use of the generic DisplayPerson method:
Private Sub ButtonOK_Click(sender As Object, e As EventArgs) Handles ButtonOK.Click Dim emp As Person emp = New Employee() With emp .Name = "Ben" .BirthDate = #1/1/2010# End With DisplayPerson(emp) End Sub
The benefit here is that you can pass a Person object or an Employee object to DisplayPerson, and the routine will work the same either way.
When you run the application now, things get interesting. You will get an error when you attempt to set the BirthDate property because it breaks your 16-year-old business rule, which is implemented in the Employee class.
This clearly demonstrates the concept of a virtual method. It is the data type of the object, in this case Employee, that is important. The data type of the variable is not the deciding factor when choosing which implementation of an overridden method is invoked.
A base class can hold a reference to any subclass object, but it is the type of that specific object which determines the implementation of the method. Therefore, you can write generic routines that operate on many types of objects as long as they derive from the same base class. Before moving on, let's change the birth date to an acceptable year such as 1975.
Earlier, you wrote code in your Employee class to overload the Name method in the base Person class. This enabled you to keep the original Name functionality, but also extend it by adding another Name method that accepts a different parameter list.
You have also overridden the BirthDate method. The implementation in the Employee class replaced the implementation in the Person class. Overriding is a related but different concept from overloading. It is also possible to both overload and override a method at the same time.
In the earlier overloading example, you added a new Name property to the Employee class, while retaining the functionality present in the base Person class. You may decide that you not only want to have your second overloaded implementation of the Name method in the Employee class, but also want to replace the existing one by overriding the existing method provided by the Person class.
In particular, you may want to do this so that you can store the Name value in the Hashtable object along with your Formal and Informal names. Before you can override the Name method, you need to add the Overridable keyword to the base implementation in the Person class.
With that done, the Name method can now be overridden by any derived class. In the Employee class, you can now override the Name method, replacing the functionality provided by the Person class. However, before doing that you'll want to expand the NameTypes Enum and add a Normal entry with a value of 3.
Now you can add code to the Employee class to implement a new Name property. Note that you are using both the Overrides keyword (to indicate that you are overriding the Name method from the base class) and the Overloads keyword (to indicate that you are overloading this method in the subclass).
This new Name property merely delegates the call to the existing version of the Name property that handles the parameter-based names. To complete the linkage between this implementation of the Name property and the parameter-based version, you need to make one more change to that original overloaded version. Update the Employee class with the new property definition:
Public Overloads Property Name(ByVal type As NameTypes) As String Get Return mNames(Type) End Get Set(ByVal value As String) If mNames.ContainsKey(type) Then mNames.Item(type) = value Else mNames.Add(type, value) End If If type = NameTypes.Normal Then MyBase.Name = value End If End Set End Property
This way, if the client code sets the Name property by providing the Normal index, you are still updating the name in the base class as well as in the Dictionary object maintained by the Employee class.
Overloading enables you to add new versions of existing methods as long as their parameter lists are different. Overriding enables your subclass to entirely replace the implementation of a base-class method with a new method that has the same method signature. As you just saw, you can even combine these concepts not only to replace the implementation of a method from the base class, but also to simultaneously overload that method with other implementations that have different method signatures.
However, anytime you override a method using the Overrides keyword, you are subject to the rules governing virtual methods—meaning that the base class must give you permission to override the method. If the base class does not use the Overridable keyword, then you can't override the method.
Sometimes, however, you may need to replace a method that is not marked as Overridable, and shadowing enables you to do just that. The Shadows keyword can be used to entirely change the nature of a method or other interface element from the base class.
However, shadowing should be done with great care, as it can seriously reduce the maintainability of your code. Normally, when you create an Employee object, you expect that it can act not only as an Employee but also as a Person, because Employee is a subclass of Person. However, with the Shadows keyword, you can radically alter the behavior of an Employee class so that it does not act like a Person. This sort of radical deviation from what is normally expected invites bugs and makes code hard to understand and maintain.
Shadowing methods is very dangerous and should be used as a last resort. It is primarily useful in cases for which you have a preexisting component, such as a Windows Forms control that was not designed for inheritance. If you absolutely must inherit from such a component, you may need to use shadowing to “rewrite” methods or properties. Despite the serious limits and dangers, it may be your only option.
In the typical case, nonvirtual methods are easy to understand. They can't be overridden and replaced, so you know that there's only one method by that name, with that method signature. Therefore, when you invoke it, there is no ambiguity about which specific implementation will be called.
The designer of a base class is typically careful when marking a method as Overridable, ensuring that the base class continues to operate properly even when that method is replaced in a subclass. Designers of base classes typically just assume that if they do not mark a method as Overridable, it will be called and not overridden. Thus, overriding a nonvirtual method by using the Shadows keyword can have unexpected and potentially dangerous side effects, as you are doing something that the base-class designer assumed would never happen.
If that isn't enough complexity, it turns out that shadowed methods follow different rules than virtual methods when they are invoked. That is, they do not act like regular overridden methods; instead, they follow a different set of rules to determine which specific implementation of the method will be invoked. The ability to call the child's implementation of a method that has been shadowed doesn't exist. Because the system isn't aware that the method could be overridden the base-class implementation is called.
To see how this works, add a new property to the base Person class using the following:
Public ReadOnly Property Age() As Integer Get Return CInt(DateDiff(DateInterval.Year, Now, BirthDate)) End Get End Property
Here you have added a new method called Age to the base class, and thus automatically to the subclass. This code has a bug, introduced intentionally for illustration. The DateDiff parameters are in the wrong order, so you will get negative age values from this routine. The bug was introduced to highlight the fact that sometimes you will find bugs in base classes that you didn't write (and which you can't fix because you don't have the source code).
The following example walks you through the use of the Shadows keyword to address a bug in your base class, acting under the assumption that for some reason you can't actually fix the code in the Person class. Note that you are not using the Overridable keyword on this method, so subclasses are prevented from overriding the method by using the Overrides keyword.
Before you shadow the method, you can see how it works as a regular nonvirtual method. First, you need to change your form to use this new value. Add a text box named TextBoxAge and a related label to the form. Next, change the Sub DisplayPerson to use the Age property. You will include the following code from to display the data on the form (code file: Form1.vb):
Private Sub DisplayPerson(ByVal thePerson As Person) With thePerson TextBoxName.Text = .Name TextBoxDOB.Text = Format(.BirthDate, "Short date") TextBoxAge.Text = .Age End With End Sub
Run the application. The age field should appear in your display as expected, though with a negative value due to the bug you introduced. There's no magic or complexity here. This is basic programming with objects, and basic use of inheritance as described earlier in this chapter.
Of course, you don't want a bug in your code, but nor do you have access to the Person class, and the Person class does not allow you to override the Age method.
As a result you can shadow the Age method within the Employee class, replacing the implementation in the Person class, even though it is not marked as Overridable. Add the following code to the Employee class:
Public Shadows ReadOnly Property Age() As Integer Get Return CInt(DateDiff(DateInterval.Year, BirthDate, Now)) End Get End Property
Technically, the Shadows keyword is not required here. Shadowing is the default behavior when a subclass implements a method that matches the name and method signature of a method in the base class. However, it is better to include the keyword. This makes it clear that you shadowed the method intentionally.
Now you are declaring the variable to be of type Person, but you are creating an object that is of data type Employee. You did this earlier in the chapter when exploring the Overrides keyword, and in that case you discovered that the version of the method that was invoked was based on the data type of the object.
If you run the application now, you will see that the rules are different when the Shadows keyword is used. In this case, the implementation in the Person class is invoked, giving you the buggy negative value. When the implementation in the Employee class is ignored, you get the exact opposite behavior of what you got with Overrides.
This is a simple case, and can be corrected by updating the display back in the main method by casting to your base type as follows (code file: Form1.vb):
Private Sub ButtonOK_Click(sender As Object, e As EventArgs) Handles ButtonOK.Click Dim emp As Person emp = New Employee() With emp .Name = "Ben" .BirthDate = #1/1/1975# End With DisplayPerson(emp) TextBoxAge.Text = CType(emp, Employee).Age End Sub
When you run the application you will see that the value of the age field is correct, as shown in . This illustrates that you just ran the implementation of the Age property from the Employee class.
In most cases, the behavior you will want for your methods is accomplished by the Overrides keyword and virtual methods. However, in cases where the base-class designer does not allow you to override a method and you want to do it anyway, the Shadows keyword provides you with the needed functionality.
The Shadows keyword can be used not only to override nonvirtual methods, but also to totally replace and change the nature of a base-class interface element. When you override a method, you are providing a replacement implementation of that method with the same name and method signature. Using the Shadows keyword, you can do more extreme things, such as change a method into an instance variable or change a property into a function.
However, this can be very dangerous, as any code written to use your objects will naturally assume that you implement all the same interface elements and behaviors as your base class, because that is the nature of inheritance. Any documentation or knowledge of the original interface is effectively invalidated because the original implementation is arbitrarily replaced.
You can entirely change the nature of the Age property to see how you can replace an interface element from the base class. For example, you could change it from a read-only property to a read-write property. You could get even more extreme—change it to a Function or a Sub.
Remove the Age property from the Employee class and replace it with the following implementation (code file: Employee.vb):
Public Shadows Property Age() As Integer Get Return CInt(DateDiff(DateInterval.Year, BirthDate, Now)) End Get Set(ByVal value As Integer) BirthDate = DateAdd(DateInterval.Year, -value, Now) End Set End Property
With this change, the very nature of the Age method has changed. It is no longer a simple read-only property; now it is a read-write property that includes code to calculate an approximate birth date based on the age value supplied.
As it stands, your application will continue to run just fine because you are using only the read-only functionality of the property in your form.
Multiple inheritance is not supported by either Visual Basic or the .NET platform itself. The idea behind multiple inheritance is that you can have a single subclass that inherits from two base classes at the same time.
For instance, an application might have a class for Customer and another class for Vendor. It is quite possible that some customers are also vendors, so you might want to combine the functionality of these two classes into a CustomerVendor class. This new class would be a combination of both Customer and Vendor, so it would be nice to inherit from both of them at once.
While this is a useful concept, multiple inheritance is complex. Numerous problems are associated with multiple inheritance, but the most obvious is the possibility of collisions of properties or methods from the base classes. Suppose that both Customer and Vendor have a Name property. CustomerVendor would need two Name properties, one for each base class. Yet it only makes sense to have one Name property on CustomerVendor, so to which base class does it link, and how will the system operate if it does not link to the other one?
These are complex issues with no easy answers. Within the object-oriented community, there is ongoing debate as to whether the advantages of code reuse outweigh the complexity that comes along for the ride.
Multiple inheritance isn't supported by the .NET Framework, so it is likewise not supported by Visual Basic, but you can use multiple interfaces to achieve an effect similar to multiple inheritance. This topic is addressed later in this chapter when discussing implementing multiple interfaces.
Unlike multiple inheritance, multilevel inheritance refers to the idea that your class can inherit methods and properties from not only its parent, but also from grandparent or further up the class hierarchy. Most of the examples discussed so far have illustrated how you can create a child class based on a single parent class. That is called single-level inheritance. In fact, inheritance can be many levels deep. These are sometimes referred to as chains of inheritance.
There is no hard-and-fast rule about how deep inheritance chains should go, but conventional wisdom and general experience with inheritance in other languages such as Smalltalk and C++ indicate that the deeper an inheritance chain becomes, the harder it is to maintain an application.
This happens for two reasons. First is the fragile base class or fragile superclass issue, discussed shortly. The second reason is that a deep inheritance hierarchy tends to seriously reduce the readability of your code by scattering the code for an object across many different classes, all of which are combined by the compiler to create your object.
One of the reasons for adopting object-oriented design and programming is to avoid so-called spaghetti code, whereby any bit of code you might look at does almost nothing useful but instead calls various other procedures and routines in other parts of your application. To determine what is going on with spaghetti code, you must trace through many routines and mentally piece together what it all means.
Object-oriented programming can help you avoid this problem. However, when you create deep inheritance hierarchies, you are often creating spaghetti code, because each level in the hierarchy not only extends the previous level's interface, but almost always also adds functionality.
Thus, when you look at a class, it may have very little code. To figure out what it does or how it behaves, you have to trace through the code in the previous four levels of classes, and you might not even have the code for some of those classes, as they might come from other applications or class libraries you have purchased.
On the one hand, you have the benefit of reusing code; but on the other hand, you have the drawback that the code for one object is actually scattered through five different classes. Keep this in mind when designing systems with inheritance—use as few levels in the hierarchy as possible to provide the required functionality.
You have seen how a subclass derives from a base class with the Person and Employee classes, but nothing prevents the Employee subclass from being the base class for yet another class, a sub-subclass, so to speak. This is not at all uncommon. In the working example, you may have different kinds of employees, some who work in the office and others who travel.
To accommodate this, you may want OfficeEmployee and TravelingEmployee classes. Of course, these are both examples of an employee and should share the functionality already present in the Employee class. The Employee class already reuses the functionality from the Person class. illustrates how these classes will be related.
The Employee is a subclass of Person, and your two new classes are both subclasses of Employee. While both OfficeEmployee and TravelingEmployee are employees, and thus also people, they are each unique. An OfficeEmployee almost certainly has a cube or office number, while a TravelingEmployee could keep track of the number of miles traveled.
Add a new class to your project and name it OfficeEmployee. To make this class inherit from your existing Employee class, add the following code to the class:
Inherits Employee
With this change, the new class now has Name, BirthDate, Age, HireDate, and Salary methods. Notice that methods from both Employee and Person are inherited. A subclass gains all the methods, properties, and events of its base classes.
You can now extend the interface and behavior of OfficeEmployee by adding a property to indicate which cube or office number the employee occupies, as follows (code file: Employee.vb):
Public Class OfficeEmployee Inherits Employee Public Property OfficeNumber() As String End Class
To see how this works, you will enhance the form to display this value. Add a new TextBox control named TextBoxOffice and an associated label so that your form looks like the one shown in . (Note also includes the data you'll see when the form is run.)
The following demonstrates how to change the code-behind for the button to use the new property (code file: Form1.vb).
Private Sub btnOK_Click(ByVal sender As System.Object, _ Private Sub ButtonOK_Click(sender As Object, e As EventArgs) Handles ButtonOK.Click Dim emp = New OfficeEmployee() With emp .Name = "Ben" .BirthDate = #1/1/1975# .OfficeNumber = "NE Corner" End With DisplayPerson(emp) TextBoxAge.Text = CType(emp, Employee).Age If TypeOf emp Is OfficeEmployee Then TextBoxOffice.Text = CType(emp, OfficeEmployee).OfficeNumber End If End Sub
You have changed the routine to declare and create an object of type OfficeEmployee—thus enabling you to make use of the new property, as well as all existing properties and methods from Employee and Person, as they've been “merged” into the OfficeEmployee class via inheritance. If you now run the application, the name, birth date, age, and office values are displayed in the form.
Inheritance like this can go many levels deep, with each level extending and changing the behaviors of the previous levels. In fact, there is no specific technical limit to the number of levels of inheritance you can implement in Visual Basic. But keep in mind that very deep inheritance chains are typically not recommended and are often viewed as a design flaw.
You have explored where it is appropriate to use inheritance and where it is not. You have also explored how you can use inheritance and multiple interfaces in conjunction to implement both is-a and act-as relationships simultaneously within your classes.
Earlier it was noted that while inheritance is an incredibly powerful and useful concept, it can also be very dangerous if used improperly. You have seen some of this danger in the discussion of the misapplication of the is-a relationship, and how you can use multiple interfaces to avoid those issues.
One of the most classic and common problems with inheritance is the fragile base-class problem. This problem is exacerbated when you have very deep inheritance hierarchies, but it exists even in a single-level inheritance chain.
The issue you face is that a change in the base class always affects all child classes derived from that base class. This is a double-edged sword. On the one hand, you get the benefit of being able to change code in one location and have that change automatically cascade through all derived classes. On the other hand, a change in behavior can have unintended or unexpected consequences farther down the inheritance chain, which can make your application very fragile and hard to change or maintain.
You have already seen how you can use the MyBase keyword to call methods on the base class from within a subclass. The MyBase keyword is one of three special keywords that enable you to interact with important object and class representations:
Earlier, you saw an example of this when you called back into the base class from an overridden method in the subclass. The MyBase keyword references only the immediate parent class, and it works like an object reference. This means that you can call methods on MyBase knowing that they are being called just as if you had a reference to an object of your parent class's data type.
The MyBase keyword can be used to invoke or use any Public, Friend, or Protected element from the parent class. This includes all elements directly on the base class, and any elements the base class inherited from other classes higher in the inheritance chain.
You already used MyBase to call back into the base Person class as you implemented the overridden Name property in the Employee class.
You can also use MyBase to call back into the base class implementation even if you have shadowed a method. The MyBase keyword enables you to merge the functionality of the base class into your subclass code as you deem fit.
The Me keyword provides you with a reference to your current object instance. In some languages they have the concept of “this” object as the current instance. Typically, you do not need to use the Me keyword, because whenever you want to invoke a method within your current object, you can just call that method directly. Occasionally you may find it helpful to better screen your IntelliSense options when typing, but even then it doesn't need to remain in your code.
To see clearly how this works, add a new method to the Person class that returns the data of the Person class in the form of a String. Remember that all classes in the .NET Framework ultimately derive from System.Object, even if you do not explicitly indicate it with an Inherits statement.
This means that you can simply override the ToString method from the Object class within your Person class as follows:
Public Overrides Function ToString() As String Return Me.Name End Function
This implementation returns the person's Name property as a result when ToString is called.
Notice that the ToString method is calling another method within your same class—in this case, the Name method.
In most cases it is redundant because Me is the default for all method calls in a class, so typically the Me keyword is simply omitted to avoid the extra typing.
Earlier, you looked at virtual methods and how they work. Because either calling a method directly or calling it using the Me keyword invokes the method on the current object. Method calls within an object conform to the same rules as an external method call.
In other words, your ToString method may not actually end up calling the Name method in the Person class if that method was overridden by a class farther down the inheritance chain.
For example, the Employee class already overloads the Name property in your Person class. However, you can also override the version supplied by Person such that it always returns the informal version of the person's name, rather than the regular name. To make it more interesting you can override the Name property of the OfficeEmployee class as follows:
Public Overloads Overrides Property Name() As String Get Return MyBase.Name(NameTypes.NickName) End Get Set(ByVal value As String) MyBase.Name = value End Set End Property
This new version of the Name method relies on the base class to actually store the value, but instead of returning the regular name on request, now you are always returning the nickname.
Before you can test this, you need to enhance the code in your form to actually provide a value for the informal name. Update the OK button code behind with the following (code file: Form1.vb):
Private Sub ButtonOK_Click(sender As Object, e As EventArgs) Handles ButtonOK.Click Dim emp = New OfficeEmployee() With emp .Name = "Ben" .Name(NameTypes.NickName) = "Benjie" .BirthDate = #1/1/1975# .OfficeNumber = "NE Corner" End With DisplayPerson(emp) TextBoxAge.Text = CType(emp, Employee).Age If TypeOf emp Is OfficeEmployee Then TextBoxOffice.Text = CType(emp, OfficeEmployee).OfficeNumber End If End Sub Private Sub DisplayPerson(ByVal thePerson As Person) With thePerson 'TextBoxName.Text = .Name TextBoxName.Text = .ToString TextBoxDOB.Text = Format(.BirthDate, "Short date") TextBoxAge.Text = .Age End With End Sub
When you run the application, the Name field displays the nickname. Even though the ToString method is implemented in the Person class, it is invoking the implementation of Name from the OfficeEmployee class. This is because method calls within a class follow the same rules for calling virtual methods as code outside a class, such as your code in the form. You will see this behavior with or without the Me keyword, as the default behavior for method calls is to implicitly call them via the current object.
Keep in mind that while methods called from within a class follow the same rules for virtual methods, this is not the case for shadowed methods.
Shadowed implementations in subclasses are ignored when calling the method from within a class higher in the inheritance chain. You will get this same behavior with or without the Me keyword.
The Me keyword exists primarily to enable you to pass a reference to the current object as a parameter to other objects or methods.
As you have seen, when you use the Me keyword or call a method directly, your method call follows the rules for calling both virtual and nonvirtual methods.
While this behavior is often useful, sometimes you will want to ensure that you truly are running the specific implementation from your class; even if a subclass overrode your method, you still want to ensure that you are calling the version of the method that is directly in your class.
Maybe you decide that your ToString implementation in Person should always call the Name implementation that you write in the Person class, totally ignoring any overridden versions of Name in any subclasses.
This is where the MyClass keyword shines. This keyword is much like MyBase, in that it provides you with access to methods as though it were an object reference—in this case, a reference to an instance of the class that contains the code you are writing when using the MyClass keyword. This is true even when the instantiated object is an instance of a class derived from your class.
You have seen that a call to ToString from within Person actually invokes the implementation in Employee or OfficeEmployee if your object is an instance of either of those types.
You can force the use of the implementation in the current class through the use of MyClass. Change the ToString method in Person as follows:
Public Overrides Function ToString() As String Return MyClass.Name End Function
You are now calling the Name method, but you are doing it using the MyClass keyword. When you run the application and click the button, the Name field in the form displays Ben rather than Benjie, proving that the implementation from Person was invoked even though the data type of the object itself is OfficeEmployee.
The ToString method is invoked from Person. Because you are using the MyClass keyword, the Name method is invoked directly from Person, explicitly defeating the default behavior you would normally expect.
As discussed in Chapter 3, you can provide a special constructor method, named New, on a class and it will be the first code run when an object is instantiated. You can also receive parameters via the constructor method, enabling the code that creates your object to pass data into the object during the creation process.
Constructor methods are affected by inheritance differently than regular methods.
Constructors do not quite follow the same rules when it comes to inheritance.
To explore the differences, implement the following simple constructor method in the Person class:
Public Sub New() Debug.WriteLine("Person constructor") End Sub
If you now run the application, you will see the text displayed in the Output window in the IDE. This occurs even though the code in your form is creating an object of type OfficeEmployee.
As you might expect, the New method from your base Person class is invoked as part of the construction process of the OfficeEmployee object—simple inheritance at work. However, interesting things occur if you implement a New method in the OfficeEmployee class itself using the the following:
Public Sub New() Debug.WriteLine("OfficeEmployee constructor") End Sub
Notice that you are not using the Overrides keyword, nor did you mark the method in Person as Overridable. These keywords have no use in this context, and in fact will cause syntax errors if you attempt to use them on constructor methods.
When you run the application now, you might expect that only the implementation of New in OfficeEmployee would be invoked. That is what would occur with a normal overridden method. However, even though New doesn't specify overloading, when you run the application, both implementations are run, and both strings are output to the output window in the IDE.
Note that the implementation in the Person class ran first, followed by the implementation in the OfficeEmployee class. This occurs because when an object is created, all the constructors for the classes in the inheritance chain are invoked, starting with the base class and including all the subclasses one by one. In fact, if you implement a New method in the Employee class, you can see that it too is invoked.
The rules governing constructors without parameters are pretty straightforward, but things get a bit more complex when you start adding parameters on your constructors.
To understand why, you need to consider how even your simple constructors are invoked. While you may see them as being invoked from the base class down through all subclasses to your final subclass, what is really happening is a bit different.
In particular, it is the subclass New method that is invoked first. However, Visual Basic automatically inserts a line of code into your routine at compile time.
For instance, in your OfficeEmployee class you have a constructor. Behind the scenes, Visual Basic inserts what is effectively a call to the constructor of your parent class on your behalf. You could do this manually by using the MyBase keyword with the following change:
Public Sub New() MyBase.New() Debug.WriteLine("OfficeEmployee constructor") End Sub
This call must be the first line in your constructor. If you put any other code before this line, you will get a syntax error indicating that your code is invalid. Because the call is always required, and because it always must be the first line in any constructor, Visual Basic simply inserts it for you automatically.
Note that if you don't explicitly provide a constructor on a class by implementing a New method, Visual Basic creates one for you behind the scenes. The automatically created method simply has one line of code:
MyBase.New()
All classes have constructor methods, either created explicitly by you as you write a New method or created implicitly by Visual Basic as the class is compiled.
By always calling MyBase.New as the first line in every constructor, you are guaranteed that it is the implementation of New in your top-level base class that actually runs first. Every subclass invokes the parent class implementation all the way up the inheritance chain until only the base class remains. Then its code runs, followed by each individual subclass, as shown earlier.
This works great when your constructors don't require parameters, but if your constructor does require a parameter, then it becomes impossible for Visual Basic to automatically make that call on your behalf. After all, how would Visual Basic know what values you want to pass as parameters?
To see how this works, change the New method in the Person class to require a Name parameter. You can use that parameter to initialize the object's Name property similar to what you see in the following:
Public Sub New(ByVal name As String) Me.Name = name Debug.WriteLine("Person constructor") End Sub
Now your constructor requires a String parameter and uses it to initialize the Name property. You are using the Me keyword to make your code easier to read. Interestingly enough, the compiler actually understands and correctly compiles the following code:
Name = name
However, that is not at all clear to a developer reading the code. By prefixing the property name with the Me keyword, you make it clear that you are invoking a property on the object and providing it with the parameter value.
At this point, your application won't compile because there is an error in the New method of the Employee class. In particular, Visual Basic's attempt to automatically invoke the constructor on the Person class fails because it has no idea what data value to pass for this new name parameter. There are three ways you can address this error:
If you make the Name parameter Optional, then you are indicating that the New method can be called with or without a parameter. Therefore, one viable option is to call the method with no parameters, so Visual Basic's default of calling it with no parameters works just fine.
If you overload the New method, then you can implement a second New method that doesn't accept any parameters, again allowing Visual Basic's default behavior to work as you have seen. Keep in mind that this solution invokes only the overloaded version of New with no parameter; the version that requires a parameter would not be invoked.
The final way you can fix the error is by simply providing a parameter value yourself from within the New method of the Employee class. Note that even though you have a constructor in the OfficeEmployee class, because MyBase always goes to the direct parent, which is the Employee class, you must add this to the Employee class. To do this, change the Employee class as follows (code file: Person.vb):
Public Sub New() MyBase.New("George") Debug.WriteLine("Employee constructor") End Sub
Obviously, you probably do not want to hard-code a value in a constructor. However, this allows you to demonstrate calling the base-class constructor with a parameter. By explicitly calling the New method of the parent class, you are able to provide it with the required parameter value. At this point, your application will compile, and run.
What isn't clear from this code is that you have now introduced a condition that can lead to a very insidious bug. The constructor in the Person class is using the Name property. However, the Name property can be overridden by the Employee class, so that implementation will be run. In this case you're going to modify the implementation such that it stops using the property defined by Person. Instead you'll add the normal name to the local property of Employee as follows:
Public Overrides Property Name As String Get Return mNames(NameTypes.Normal) End Get Set(value As String) If mNames.ContainsKey(NameTypes.Normal) Then mNames.Item(NameTypes.Normal) = value Else mNames.Add(NameTypes.Normal, value) End If End Set End Property
Unfortunately, this new implementation makes use of a Dictionary object, which isn't available yet! It turns out that any member variables declared in a class with the New statement, such as the Dictionary object in Employee, won't be initialized until after the constructor for that class has completed.
Because you are still in the constructor for Person, there's no way the constructor for Employee can be complete. To resolve this, you would need to change the implementation of the Name method to create the required Dictionary when called. Instead of presuming that the local field existed, your code would create it. This isn't a best practice in that none of this is necessary if your code simply continues to use the base class's storage for the default Name property.
The key is that your code can ensure that a Dictionary object is created in the Employee class code, even though its constructor hasn't yet run. For now you can comment out the Override of the Name property in the Employee class.
You have seen how a subclass automatically gains all the Public methods and properties that compose the interface of the base class. This is also true of Friend methods and properties; they are inherited as well and are available only to other code in the same assembly as the subclass.
Private methods and properties are not exposed as part of the interface of the subclass, meaning that the code in the subclass cannot call those methods, nor can any code using your objects. These methods are available only to the code within the base class itself.
Sometimes you will want to create methods in your base class that can be called by a subclass, as well as the base class, but not by code outside of those classes. Basically, you want a hybrid between Public and Private access modifiers—methods that are private to the classes in the inheritance chain but usable by any subclasses that might be created within the chain. This functionality is provided by the Protected scope.
Protected methods are very similar to Private methods in that they are not available to any code that calls your objects. Instead, these methods are available to code within the base class and to code within any subclass. lists the available scope options:
Scope | Description |
Private | Only available to code within your class. |
Protected | Available only to classes that inherit from your class. |
Friend | Available to code within your project/component. |
Protected Friend | Available to classes that inherit from your class, in any project. Also available to code within your project/component that doesn't inherit from your class. This is a combination of Protected and Friend. |
Public | Available outside your class and project. |
To see how the Protected scope works, add an Identity field to the Person class as follows:
Protected Property Identity As String
This data field represents some arbitrary identification number or value assigned to a person. This might be a social security number, an employee number, or whatever is appropriate.
The interesting thing about this value is that it is not currently accessible outside your inheritance chain. For instance, if you try to use it from your code in the form, you will discover that there is no Identity property on your Person, Employee, or OfficeEmployee objects.
However, there is an Identity property now available inside your inheritance chain. The Identity property is available to the code in the Person class, just like any other method. Interestingly, even though Identity is not available to the code in your form, it is available to the code in the Employee and OfficeEmployee classes, because they are both subclasses of Person. Employee is directly a subclass, and OfficeEmployee is indirectly a subclass of Person because it is a subclass of Employee.
Thus, you can enhance your Employee class to implement an EmployeeNumber property by using the Identity property. To do this, add the following code to the Employee class:
Public Property EmployeeNumber() As Integer Get Return CInt(Identity) End Get Set(ByVal value As Integer) Identity = CStr(value) End Set End Property
This new property exposes a numeric identity value for the employee, but it uses the internal Identity property to manage that value. You can override and shadow Protected elements just as you do with elements of any other scope.
Up to this point, you've focused on methods and properties and how they interact through inheritance. Inheritance, and, in particular, the Protected scope, also affects instance variables and how you work with them.
Though it is not recommended, you can declare variables in a class using Public scope. This makes the variable directly available to code both within and outside of your class, allowing any code that interacts with your objects to directly read or alter the value of that variable.
Variables can also have Friend scope, which likewise allows any code in your class or anywhere within your project to read or alter the value directly. This is also generally not recommended because it breaks encapsulation.
Of course, you know that variables can be of Private scope, and this is typically the case. This makes the variables accessible only to the code within your class, and it is the most restrictive scope.
As with methods, however, you can also use the Protected scope when declaring variables. This makes the variable accessible to the code in your class and to the code in any class that derives from your class—all the way down the hierarchy chain.
Sometimes this is useful, because it enables you to provide and accept data to and from subclasses, but to act on that data from code in the base class. At the same time, exposing variables to subclasses is typically not ideal, and you should use Property methods with Protected scope for this instead, as they allow your base class to enforce any business rules that are appropriate for the value, rather than just hope that the author of the subclass provides only good values.
So far, you've looked at methods, properties, and variables in terms of inheritance—how they can be added, overridden, overloaded, and shadowed. In Visual Basic, events are also part of the interface of an object, and they are affected by inheritance as well.
Chapter 3 introduced how to declare, raise, and receive events from objects. You can add such an event to the Person class by declaring it at the top of the class. Then, you can raise this event within the class anytime the person's name is changed as follows:
Public Event NameChanged(ByVal newName As String) Private mName as String Public Overridable Property Name As String Get Return mName End Get Set(ByVal value As String) mName = value RaiseEvent NameChanged(mName) End Set End Property
At this point, you can modify Form1 to handle this event. The nice thing about this is that your events are inherited automatically by subclasses—meaning your Employee and OfficeEmployee objects will also raise this event when they set the Name property of Person. Thus, you can change the code in your form to handle the event, even though you are working with an object of type OfficeEmployee.
One caveat you should keep in mind is that while a subclass exposes the events of its base class, the code in the subclass cannot raise the event. In other words, you cannot use the RaiseEvent method in Employee or OfficeEmployee to raise the NameChanged event. Only code directly in the Person class can raise the event.
Fortunately, there is a relatively easy way to get around this limitation. You can simply implement a Protected method in your base class that allows any derived class to raise the method. In the Person class, you can add a new event and protected method as follows:
Public Event PropertyChanged(PropName As String, NewValue As Object) Protected Sub OnDataChanged(PropName As String, NewValue As Object) RaiseEvent PropertyChanged(PropName, NewValue) End Sub
You can now use this method from within the Employee class as follows to indicate that EmployeeNumber has changed.
Public Property EmployeeNumber() As Integer Get Return CInt(Identity) End Get Set(ByVal value As Integer) Identity = CStr(value) OnDataChanged("EmployeeNumber", value) End Set End Property
Note that the code in Employee is not raising the event, it is simply calling a Protected method in Person. The code in the Person class is actually raising the event, meaning everything will work as desired.
You can enhance the code in Form1 to receive the event. First, create the following method to handle the event:
Private Sub OnDataChanged(ByVal PropertyName As String, ByVal NewValue As Object) MessageBox.Show("New " & PropertyName & ": " & NewValue.ToString()) End Sub
Then, link this handler to the event using the AddHandler method. Finally, ensure that you are changing and displaying the EmployeeNumber property as follows:
Private Sub ButtonOK_Click(sender As Object, e As EventArgs) Handles ButtonOK.Click Dim emp = New OfficeEmployee() AddHandler emp.PropertyChanged, AddressOf OnDataChanged With emp .Name = "Ben" .Name(NameTypes.NickName) = "Benjie" .BirthDate = #1/1/1975# .EmployeeNumber = 7 .OfficeNumber = "NE Corner" End With DisplayPerson(emp) TextBoxAge.Text = CType(emp, Employee).Age If TypeOf emp Is OfficeEmployee Then TextBoxOffice.Text = CType(emp, OfficeEmployee).OfficeNumber End If End Sub
When you run the application and click the button now, you will get message boxes displaying the change to the EmployeeNumber property.
Chapter 3 explored shared methods and how they work: providing a set of methods that can be invoked directly from the class, rather than requiring that you create an actual object.
Shared methods are inherited just like instance methods and so are automatically available as methods on subclasses, just as they are on the base class. If you implement a shared method in a base class, you can call that method using any class derived from that base class.
Like regular methods, shared methods can be overloaded and shadowed. They cannot, however, be overridden. If you attempt to use the Overridable keyword when declaring a shared method, you will get a syntax error.
Shared methods can be overloaded using the Overloads keyword in the same manner as you overload an instance method. This means that your subclass can add new implementations of the shared method as long as the parameter list differs from the original implementation.
Shared methods can also be shadowed by a subclass. This allows you to do some very interesting things, including converting a shared method into an instance method or vice versa. You can even leave the method as shared but change the entire way it works and is declared. In short, just as with instance methods, you can use the Shadows keyword to entirely replace and change a shared method in a subclass.
So far, you have seen how to inherit from a class, how to overload and override methods, and how virtual methods work. In all of the examples so far, the parent classes have been useful in their own right and could be instantiated and do some meaningful work. Sometimes, however, you want to create a class such that it can only be used as a base class for inheritance.
The current Person class is being used as a base class, but it can also be instantiated directly to create an object of type Person. Likewise, the Employee class is also being used as a base class for the OfficeEmployee class you created that derives from it.
If you want to make a class act only as a base class, you can use the MustInherit keyword, thereby preventing anyone from creating objects based directly on the class, and requiring them instead to create a subclass and then create objects based on that subclass.
This can be very useful when you are creating object models of real-world concepts and entities. The following snippet demonstrates how you should change Person to use the MustInherit keyword.
Public MustInherit Class Person
This has no effect on the code within Person or any of the classes that inherit from it, but it does mean that no code can instantiate objects directly from the Person class; instead, you can only create objects based on Employee or OfficeEmployee.
This does not prevent you from declaring variables of type Person; it merely prevents you from creating an object by using New Person. You can also continue to make use of Shared methods from the Person class without any difficulty.
Another option you have is to create a method (Sub, Function, or Property) that must be overridden by a subclass. You might want to do this when you are creating a base class that provides some behaviors but relies on subclasses to also provide other behaviors in order to function properly. This is accomplished by using the MustOverride keyword on a method declaration.
If a class contains any methods marked with MustOverride, the class itself must also be declared with the MustInherit keyword or you will get a syntax error.
This makes sense. If you are requiring that a method be overridden in a subclass, it stands to reason that your class can't be directly instantiated; it must be subclassed to be useful.
You can see how this works by adding a LifeExpectancy method in Person that has no implementation and must be overridden by a subclass as follows:
Public MustOverride Function LifeExpectancy() As Integer
Notice that there is no End Function or any other code associated with the method. When using MustOverride, you cannot provide any implementation for the method in your class. Such a method is called an abstract method or pure virtual function, as it defines only the interface and no implementation.
Methods declared in this manner must be overridden in any subclass that inherits from your base class. If you do not override one of these methods, you will generate a syntax error in the subclass and it won't compile. You need to alter the Employee class to provide an implementation similar to the following:
Public Overrides Function LifeExpectancy() As Integer Return 90 End Function
Your application will compile and run at this point, because you are now overriding the LifeExpectancy method in Employee, so the required condition is met.
You can combine these two concepts, using both MustInherit and MustOverride, to create something called an abstract base class, sometimes referred to as a virtual class. This is a class that provides no implementation, only the interface definitions from which a subclass can be created, as shown in the following example:
Public MustInherit Class AbstractBaseClass Public MustOverride Sub DoSomething() Public MustOverride Sub DoOtherStuff() End Class
This technique can be very useful when creating frameworks or the high-level conceptual elements of a system. Any class that inherits AbstractBaseClass must implement both DoSomething and DoOtherStuff; otherwise, a syntax error will result.
In some ways, an abstract base class is comparable to defining an interface using the Interface keyword. The Interface keyword is discussed in detail later in this chapter. You could define the same interface shown in this example with the following code:
Public Interface IAbstractBaseClass Sub DoSomething() Sub DoOtherStuff() End Interface
Any class that implements the IAbstractBaseClass interface must implement both DoSomething and DoOtherStuff or a syntax error will result, and in that regard this technique is similar to an abstract base class.
If you want to prevent a class from being used as a base class, you can use the NotInheritable keyword. For instance, you can change your OfficeEmployee to prevent inheritance with the following keyword:
Public NotInheritable Class OfficeEmployee
At this point, it is no longer possible to inherit from this class to create a new class. Your OfficeEmployee class is now sealed, meaning it cannot be used as a base from which to create other classes.
If you attempt to inherit from OfficeEmployee, you will get a compile error indicating that it cannot be used as a base class. This has no effect on Person or Employee; you can continue to derive other classes from them.
Typically, you want to design your classes so that they can be subclassed, because that provides the greatest long-term flexibility in the overall design. Sometimes, however, you want to ensure that your class cannot be used as a base class, and the NotInheritable keyword addresses that issue.
Finish the wizard. The Customer class will be displayed as an available data source, as shown in , when you are working in Design view for Form1.
Click on Customer in the Data Sources window. Customer should change its display to a combo box. Open the combo box and change the selection from DataGridView to Details. This way, you get a Details view of the object on your form. Open the designer for Form1 and drag the Customer class from the Data Sources window onto the form. The result in design view should look something like the dialog shown in .
All you need to do now is add code to create an instance of the Customer class to act as a data source for the form. Double-click on the form to bring up its code window and add the following:
Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) _ Handles MyBase.Load Me.CustomerBindingSource.DataSource = New Customer() End Sub End Class
You are using the ability of Windows Forms to data-bind to a property on an object. You'll learn more about data binding later. For now, it is enough to know that the controls on the form are automatically tied to the properties on your object.
Now you have a simple user interface (UI) that both displays and updates the data in your Customer object, with that object providing the UI developer with an abstract representation of the customer. When you run the application, you will see a display like the one shown in .
This chapter demonstrated how Visual Basic enables you to create and work with classes and objects. Visual Basic provides the building blocks for abstraction, encapsulation, polymorphism, and inheritance.
You have learned how to create both simple base classes as well as abstract base classes. You have also explored how you can define formal interfaces, a concept quite similar to an abstract base class in many ways.
You also walked through the process of subclassing, creating a new class that derives both interface and implementation from a base class. The subclass can be extended by adding new methods or altering the behavior of existing methods on the base class.
By the end of this chapter, you have seen how object-oriented programming flows from the four basic concepts of abstraction, encapsulation, polymorphism, and inheritance. The chapter provided basic information about each concept and demonstrated how to implement them using Visual Basic.
By properly applying object-oriented design and programming, you can create very large and complex applications that remain maintainable and readable over time. Nonetheless, these technologies are not a magic bullet. Improperly applied, they can create the same hard-to-maintain code that you might create using procedural or modular design techniques.
It is not possible to fully cover all aspects of object-oriented programming in a single chapter. Before launching into a full-blown object-oriented project, it is highly recommend that you look at other books specifically geared toward object-oriented design and programming.
In the next chapter you are going to explore some of the more advanced language concepts, like Lambdas, that have been introduced to Visual Basic.