Inheriting Riches
Wayne S. Freeze
Welcome to VB.NEW, a monthly column dedicated to keeping you up-to-date with all that's new, different, and exciting for Visual Basic developers in the brave new .NET world. Whether you're just learning VB.NET or have applications to migrate to the new platform, VB.NEW will help you understand how and when to best put all of those new features to use. Wayne S. Freeze offers the first installment in this regular series.
Got the VB.NET blues? Then you need VB.NEW! VB.NEW is a column devoted to exploring the new features found in VB.NET. Over the coming months, I'll discuss topics such as overloading, namespaces, assemblies, new classes, new statements, and lots more to help you use VB.NET effectively. In this first column, I'm going to focus on what's probably the biggest single change to Visual Basic since Basic was updated to run under Windows: inheritance.
Object-oriented programming involves a different way of thinking. In the past, you created objects by creating a Class module. You had to explicitly define every property, method, and event. Many programmers exploited this capability to create classes that were merely collections of related functions without any true organization. While this is a pretty good technique, it isn't truly object-oriented programming. In order to do object-oriented programming, you must have inheritance. But what, exactly, is inheritance?
Inheritance is like creating a new class by copying an existing class. But it's a special kind of copying. With inheritance, you don't simply duplicate all of the original's stuff in the copy. Instead, the copy (or child) has references that point back to the original (or parent's) properties and methods. This is important, because this way, if the parent is changed, the child is automatically updated with those changes.
Inheritance makes reuse of existing code easy, since your new class automatically includes all of the properties, methods, and events from the parent class. Then you can add, replace, or modify the properties, methods, and events with your own code, while leaving the original code intact.
Classy programming
A Class module is just a template that's used to create an object. It defines the various properties, methods, and events that are associated with the object. If you've used Class modules in VB6, all of the same concepts apply to VB.NET. However, there have been quite a few changes in the syntax. Some of these changes are just different ways of doing the same thing, while others are used to implement inheritance and other new features.
One key difference between classes in VB6 and in VB.NET is the concept of a constructor. A constructor is similar to the Class_Initialize event in VB6 in that it often contains initialization code that's called when a new instance of an object is created.
You might wonder whether there's a corresponding destructor routine, which would clean up after the object is no longer needed. While many object-oriented programming languages include destructors, they don't exist in VB.NET. The Common Language Runtime (CLR) includes a tool known at the Garbage Collector, which will automatically detect and destroy any objects that you create and no longer use.
Inheritance hierarchies
The trick to designing object-oriented programs is to think in terms of hierarchies. Each object is inherited from a higher-level object that is itself, in turn, inherited from a still higher-level object. The root class is the most general, and as you move down the hierarchy, the objects become more specific. VB.NET encourages this by making it easy to take Microsoft-supplied classes and inherit from them to create customized children as you see fit. VB6 used a simplified version of this concept, by allowing you to create user controls by using existing controls. However, you still had to write a lot of code to make everything work properly. You had to define the properties and methods that you needed and include code that passed the request on to the base control for execution.
With true inheritance, you don't need the code to pass along the information to the base object. Your object automatically uses the code in the base object unless you explicitly override it. This means that you have to write less code, which translates into fewer places for bugs in your code.
An inheritance example
In order to better understand inheritance, let's go through a simple example. Assume that you have the following class, which is called Person. This class consists of two properties, FirstName and LastName, and one method, FullName:
Public Class Person
Public FirstName As String
Public LastName As String
Public Function FullName() As String
FullName = FirstName & " " & LastName
End Function
End Class
Now suppose you want to create another class called Employee, with the properties FirstName and LastName used in Person, plus a new property called EmployeeNumber. And you still want to be able to use that FullName function. You can type in the three property definitions and copy the function, which duplicates work you've already completed, or you can inherit the Person class and add the new property as shown in the following class definition:
Public Class Employee
Inherits Person
Public EmployeeNumber As Integer
End Class
That's all there is to it! Now Employee has all of the properties and methods of Person, plus its own EmployeeNumber property.
Overriding methods
Another advantage of inheritance is the ability to replace a method in the base class with one that works differently in the inherited class. This technique is called overriding.
Let's add another class called Customer to the example. This class, too, will inherit from Person, just as Employee did. It will add a CustomerNumber property. In addition, the FullName method should append the customer number at the end of the customer's name. The class that follows shows how to inherit the Person class and override the FullName method with a new method:
Public Class Customer
Inherits Person
Public CustomerNumber As Integer
Public Overrides Function FullName() As String
FullName = MyBase.FullName & _
"(" & CStr(CustomerNumber) & ")"
End Function
End Class
The first thing you should notice with this class is that the function definition includes the Overrides keyword. This keyword means that the function replaces the corresponding function in the base class. The next thing is that there's a reference to MyBase in the body of the function. This is how you reference properties and methods in the base class even though they have the same name in the current class. Thus you can continue to use Person's FullName method to put the names together and simply append CustomerNumber to the end of the result.
In order to use the Overrides keyword, the corresponding subroutine or function must be marked as Overridable in the base class. Thus, the FullName function definition in the Person class would need to look like this:
Public Overridable Function FullName() As String
Other classic options
In addition to the Overridable and Overrides keywords, there are two other keywords that control how you can override a method. The first is NotOverridable, which means that the method can't be overridden in an inherited class. The second is MustOverride, which means that the method must be overridden in the inherited class.
When you declare a class, you can specify a set of modifiers that describe how the class can be used. By default, any class can be inherited by another class. You can prevent this by preceding the Class keyword with NotInheritable.
Likewise, you can prevent someone from using the class directly by using the MustInherit keyword. MustInherit works well with the MustOverride keyword on particular methods in the class. This is useful if you develop a general-purpose class that you intend for other developers to inherit and extend by replacing certain methods with new ones that are specific to the inherited class.
The Class, Sub, and Function statements also include keywords that control the visibility of that particular definition. While these keywords don't affect inheritance directly, you should be familiar with them, since they control the visibility of the item with which they're associated.
Public and Private control whether the class or method is visible outside the item's current scope. A Private method is visible only inside the current class, while a Private class is visible only to the current module. Of course, if the current class is nested inside another class, it will be visible only to the outer class.
While VB6 supported the Friend keyword, it wasn't used a lot. Its visibility is somewhere between Public and Private. For code outside the current project, the class or method is considered Private, while for code inside the current project, the class or method is considered Public.
VB.NET introduces a new keyword called Protected, which marks a method as visible to an inherited class. This means you can create methods that can only be used within the inherited class. If you merely created an instance of that class, then the methods would be hidden. This is a great approach if you want to provide access to helper routines you used in the base class to someone who wants to derive a new class from it.
You can use the Protected Friend keyword to limit the access of Protected methods to the current project. This is useful if you have a set of methods you need to use within the project, yet you don't want people using them from outside the project.
The root of all objects
In VB.NET, everything is treated like an object. This includes not only things like forms and controls (which you might expect), but also variables. The Object data type has replaced the Variant data type as the default data type in Visual Basic. All classes defined in the .NET architecture are inherited either directly or indirectly from the Object data type. Understanding inheritance is critical to writing effective VB.NET applications.
While inheritance is a great concept, you might be wondering how you'd use in it your applications. After all, if you haven't needed it so far, why should you start using it now?
One of the classic problems with designing applications in Visual Basic is the amount of work needed to present a common look to all of the forms in an application. Generally, a VB6 developer will create a form that can be loaded into a project or used as a template. The form includes the menu items and controls necessary to present a common look and feel across the application.
However, this approach means that the form is frozen when it's incorporated into a program. Any changes made to the form template after the form was loaded must be manually made to each specific instance of the form. This can be a very serious problem if your application uses a lot of standard forms.
In VB.NET, forms are now a class that can be inherited just like any other class. Thus, you can easily create a standard form object that can be shared by all of the programs in an application. Each program is free to add controls to the form or modify the controls already on the form. However, the code associated with the base form is independent of the code used in the program. This allows the base form to be updated as necessary, and the updated code will automatically be incorporated into any form that inherits the base form. Microsoft refers to this technique as visual inheritance.
Another example of how inheritance might be used in your application comes from how errors are handled in VB.NET. The Exception class contains basic error information about an error that occurred in your program. It includes such information as the text message associated with the error, a link to a Help file, and a stack trace that helps identify where the error occurred.
However, other classes frequently inherit the Exception class so that they may include additional information about an error. For example, the ADOException is indirectly derived from the Exception class and contains additional information such as the specific error code associated with the error and a reference to an ADOErrors object, which documents other errors that occurred during the last database request.
Having specific classes for errors makes it easier to handle error conditions in VB.NET, which is the topic for my next column. In the meantime, if you have questions or comments, please feel to send them to me at WFreeze@JustPC.com. I'd love to hear from you!