Object-Oriented Programming in Visual Basic .NET
InStep Technologies, Inc.
Summary: This article discusses the many new object-oriented programming techniques available in Microsoft Visual Basic .NET. (11 printed pages)
The original versions of Microsoft® Visual Basic® provided a mechanism for defining data structures in a user-defined type (UDT). A UDT encapsulates the data, but not the processing associated with that data. Processing was defined in global standard modules, often called BAS modules because of their .bas extension.
The release of Visual Basic 4 dawned a new age for Visual Basic developers. Visual Basic took its first steps toward becoming an object-oriented programming (OOP) language by providing object-oriented features such as class modules. A class module defines data as properties and the processing associated with that data as methods. By defining a class for each business entity, encapsulating data in properties and processing in methods, Visual Basic developers had object-based development.
As Visual Basic evolved from version 4 to version 6, Visual Basic developers expanded their knowledge of OO to include component-based development (CBD) techniques. With CBD, Visual Basic developers could build complete three-tiered applications for Microsoft Windows® and the Web. This type of development was so common that Microsoft provided a design pattern known as the Microsoft DNA architecture.
Visual Basic .NET provides another leap in Visual Basic development capabilities and features and provides for true object-oriented programming, as detailed in this article.
For a programming language to be a true OOP language, the language must meet the following criteria:
- Abstraction—Abstraction manages the complexities of a business problem by allowing you to identify a set of objects involved with that business problem.
- Encapsulation—Encapsulation hides the internal implementation of an abstraction within the particular object.
- Polymorphism—Polymorphism provides for multiple implementations of the same method. For example, different objects can have a Save method, each of which perform different processing.
- Inheritance—The excitement of Visual Basic .NET lies in inheritance. Visual Basic 5 introduced the concept of interface inheritance, which allows you to reuse the interface of a class, but not its implementation. Visual Basic .NET provides for true implementation inheritance whereby you can reuse the implementation of a class.
Now let's look at doing object-oriented programming in .NET.
Visual Basic .NET is not Visual Basic 6 with inheritance tacked onto it. Rather, Visual Basic .NET has been entirely rewritten to be fully object-oriented. In fact, everything in Visual Basic .NET can be treated as an object. Yes, even your strings and integers can be accessed as objects in Visual Basic .NET.
To demonstrate this, start a new Visual Basic .NET console application project. In the Main subroutine, enter this code:
Dim i As Integer MsgBox(i.MinValue)
The first hint that your integer is treated as an object is the list of properties and methods that appear when you type the dot after the i. Select one of the properties, such as the MinValue shown in this example. Then run the application and you will get a message box containing the value of the selected integer property.
.NET has predefined classes for the intrinsic data types, but what about the classes that you create? Let's walk through an example to demonstrate how to create classes in Visual Basic .NET and inherit from them to leverage some of the new OOP features.
The basic purpose of a class has not changed in Visual Basic .NET. You still create classes for your business objects and for any supporting objects that you may need for your application. The primary changes from Visual Basic 6 to Visual Basic .NET involve syntax and some new features.
In Visual Basic 6, you create a class by creating a class module: one class, one class module. This is no longer the case in Visual Basic .NET. You can create any number of classes within a single code file. You can even create classes within classes. But let's start out with a simple example.
Adding a class to a Visual Basic .NET project is very similar to Visual Basic 6. However, instead of getting an empty code file, your class will appear with the following code:
Public Class CCustomer End Class
Tip The current Microsoft convention is to define the class names without a prefix. Following that convention, this class name would be Customer instead of CCustomer. While this convention is more user-friendly when creating objects using retail products such as Microsoft Word and Excel, maintenance and support of enterprise systems can benefit from the additional information that a prefix provides.
If you want to add a second class to the same file, just add another class statement:
Public Class CCustomer End Class Public Class CContact End Class
Tip Normally, a class should be defined in its own code file. Only put classes together if they are tightly coupled. For example, invoice and invoice line item could be two classes within one code file because you would normally never use invoice line item without invoice. If you would use customer contacts (CContact) separate from customers then the CContact class should be separate from the CCustomer class.
You can then add properties or methods to the class. As with Visual Basic 6, you normally define a property by declaring a private variable and public Property procedures. In Visual Basic .NET, you would define a Name property as follows:
Private m_sName As String Property Name() As String Get Return m_sName End Get Set(ByVal Value As String) m_sName = Value End Set End Property
There are only two types of Property procedures in Visual Basic .NET, Get and Set. The Get procedure retrieves the property value from the class and the Set procedure assigns the property. Visual Basic 6 provided a property Let statement that handled intrinsic data types while the Set statement worked with objects. Now that everything in Visual Basic .NET is basically an object, there is no need for the Let statement.
Notice that the syntax for a property procedure is also changed. Both the Get and Set are contained within one property statement. No more possibility of a mismatch in data types between property Get and Set. This makes these statements easier to maintain.
Tip In a three-tiered or N-tiered application, your classes may be stateless, meaning that they have no properties. This provides more efficient use of your classes within middle-tier components.
The syntax for a simple method is nearly identical to prior versions of Visual Basic. The only difference you may notice is the Return keyword. You can use Return to return a value from a function instead of using the function name. The following example demonstrates a simple method:
Public Function SayHello() As String If Name <> "" Then Return "Hello " & Name Else Return "Hello World" End If End Function
At this point, you have a class with a property and method and the result looks similar to a Visual Basic 6 class. Let's look at some of the new Visual Basic .NET features.
In Visual Basic 6, when you create an instance of a class the Initialize event is generated. You can put code into the Initialize event to initialize the object. For example, you may want to define default object data, open database connections, or create related objects. However, you cannot pass anything to the Initialize event. This makes it difficult to initialize the object with specific parameters.
Visual Basic .NET introduces true constructors that are executed whenever a new instance of the class is created. These constructors are defined with a subroutine named New:
Public Sub New() ' Perform initialization Debug.WriteLine("I am alive") End Sub
You can pass data to a constructor for more flexibility and power in initializing the object. Constructors with parameters are called parameterized constructors. For example:
Public Sub New(ByVal sName As String) ' Assign the name Name = sName 'Other initialization Debug.WriteLine(Name & " is alive") End Sub
In this example, the customer name is passed in to the constructor. That name is then used to initialize the Name property defined with the Property procedure.
Both of these constructors can be define for one class. Actually, any number of constructors can be defined for a class as long as they each have different parameters. This feature is called overloading. The appropriate constructor is called based on the data passed to the constructor.
You do not have to define a constructor. If you don't create one, a default constructor is used.
Instead of a Terminate event, Visual Basic .NET provides a Finalize destructor. This destructor is called when the .NET garbage collector determines that the object is not longer needed. There may be a delay between the time an object is terminated and the time the garbage collector actually destroys the object.
Tip You should not normally use a Finalize destructor because of this delay and the additional processing required by the system to manage objects with a Finalize destructor. Use the Dispose destructor instead.
In order to better manage the resources used by your class, implement the IDisposable interface and the Dispose destructor:
Implements IDisposable Public Sub Dispose() Implements System.IDisposable.Dispose ' Perform termination End Sub
This destructor is not called automatically, so it must be explicitly called as shown in the next section.
Tip The Dispose destructor is not required, but it is recommended. By implementing the Dispose destructor in every class, even if it does not do anything, developers can routinely can call the Dispose method on any object.
In order to use a class, you first need to create an object from the class. In Visual Basic 6, the recommended syntax for creating an object from a class is:
Private m_oCustomer as CCustomer Set m_oCustomer = New CCustomer
This syntax is almost identical in Visual Basic .NET. Since everything is basically an object in Visual Basic .NET, there is no need for two different types of assignment, so there is no longer a need for the Set keyword:
Private m_oCustomer as CCustomer m_oCustomer = New CCustomer()
Notice the parenthesis when creating the object. If you defined a parameterized constructor for the class, you can pass the parameter(s) to the constructor within the parentheses:
m_oCustomer = New CCustomer("Acme Corporation")
With Visual Basic .NET, you can combine the object variable declaration and the object creation using the New keyword:
Private m_oCustomer As CCustomer = New CCustomer()
The object is created when this declaration is executed. The shorthand form of this syntax is:
Private m_oCustomer As New CCustomer()
If you need to pass parameters, this syntax becomes:
Private m_oCustomer As New CCustomer("Acme Corporation")
Tip You cannot put a Try... Catch block for error handling around a module-level declaration. This restriction makes this style of object creation less useful. In production-quality applications, you may want to stick to the tried and true approach of first declaring the module-level variable and then creating an instance with the New keyword in order to support full-featured error handling.
In the declarations section:
Private m_oCustomer as CCustomer
Within a routine:
Try m_oCustomer = New CCustomer() Catch e As Exception Debug.WriteLine(e.Message) End Try
Continuing with the example, select your favorite style of object creation and add it to the Sub Main.
When you have finished using an object, you can call the object's Dispose method to free the resources used by the object, assuming that a Dispose method was implemented for the object. You can then set the object variable to Nothing:
m_oCustomer.Dispose() m_oCustomer = Nothing
The object is then destroyed when the garbage collector detects that the object is no longer used.
Tip Unlike Visual Basic 6, an object in .NET is not destroyed the moment that the object variable is set to Nothing. Rather, it will be destroyed when the garbage collector detects and destroys it. The garbage collector will destroy an orphaned object, whether or not you set the object variable to Nothing.
At this point, you should be able to execute the sample application and see the debug messages appear in the Output window.
Every object in .NET is inherited from a base class called System.Object. This class is part of the Microsoft .NET Framework and contains the basic properties and methods required for a .NET object.
Any public properties or methods defined in System.Object are automatically provided in any object you create. For example, System.Object contains a default constructor. If your object has no constructor, it is still constructed because System.Object provides for the construction.
Many of the public properties and methods of System.Object have a default implementation. This means that you don't have to write any code to use them. For example:
The ToString method provides the name of the component and class associated with m_oCustomer. You can override the default behavior of the ToString method with the Overrides keyword. This allows you to define your own implementation of some System.Object properties and methods.
Public Overrides Function ToString() As String Return Name End Function
The ToString method now returns the name of the customer instead of the name of the component and class.
The most talked about new OOP feature in Visual Basic .NET is inheritance. To fully understand inheritance, let's expand our example.
The Customer class example presented earlier is a small part of a complete Customer Billing System. This system tracks customers, manages customer orders, and generates bills in the form of invoices. The system is complete and delivered to the users. As is typical, after a few weeks of working with the system the users define additional requirements. They now need the code to work differently depending on the type of customer. Normal customers have a standard set of business rules. Educational customers have more rules, some different data items and special discounts. Government customers have even more business rules, other data items, and a steeper discount. The relationship between the customer, educational customer, and government customer are depicted using the Unified Modeling Language (UML) and modeled in Figure 1. But how will you implement this change?
Figure 1. Example of a complete customer billing system
One solution is to modify the existing Customer class to contain all of the information for all of the customer types. You can use Select/Case statements or If statements to manage the logic to handle the different types. But this can be tedious and complex, especially if there are a lot of different rules or processing associated with the different types.
A second option is to create a separate class for each customer type, including the "normal" type. Each class can then contain its own logic for handling that particular customer type. But if there are similarities in some of the logic, you may have duplication in the separate classes.
With Visual Basic .NET, there is now a third choice: inheritance. With inheritance, you can define a common class that is the basis of other classes. This class is called a base or parent class. In this example, you would create a common Customer class.
You then create any specialized classes that inherit from the base class. These classes are called derived or child classes. These classes inherit functionality from the base class but can override base class functionality to perform specialized processing.
To try this, let's use the Customer class as the base class. Add another method to the Customer class to calculate a discount:
Public Overridable Function CalculateDiscount(ByVal dAmt As Decimal) As Decimal ' Standard discount is no discount ' Return the passed in amount Return dAmt End Function
This method uses the Overridable keyword. This keyword means that any derived classes can override this method and define their own implementation.
Tip If you build a class that may be used as a base class, be sure to use the Overridable keyword on any function that a derived class may wish to override.
The derived class is very simple:
Public Class CEdCustomer: Inherits CCustomer Public Sub New() MyBase.New() End Sub Public Overrides Function CalculateDiscount(ByVal dAmt As Decimal) As Decimal Dim newAmt As Decimal newAmt = dAmt * CDec(0.9) Return newAmt End Function End Class
The Inherits keyword defines the parent or base class. All of the public properties and methods of the base class are accessible to this derived class.
The first method in this derived class is the constructor. This constructor uses the MyBase keyword to call the constructor in the base class. The default constructor for the parent class is automatically called when the child is constructed, but you can manually specify the parent's constructor.
The Overrides keyword on the second function denotes that this is overriding the function defined within the base class. This allows a derived class to provide its own implementation of a particular function.
Tip If you do not put the Overrides keyword on the function in the derived class, the function is assumed to shadow the original function. A shadowed function is a function in the derived class that has the same name as a function in the base class but is not intended to override the base class function.
There are several ways to create an instance of the derived class:
- Declare an object variable as the derived class and then create the instance of the derived class:
Private m_oEdCustomer As CEdCustomer m_oEdCustomer = New CEdCustomer()
- Declare an object variable as the base class and then create the instance of the derived class:
Private m_oCustomer As CCustomer m_oCustomer = New CEdCustomer()
The first technique declares the object to be of type CEdCustomer, thereby defining the object to be of a specific type. The derived object has access to any public properties and methods of the Customer base class, any properties and methods overridden in the EdCustomer class, any properties and methods shadowed in the EdCustomer class, and any public properties and methods of the EdCustomer class.
The second technique declares the object to be of type CCustomer, allowing any type of Customer object to be defined and to leverage polymorphism. This allows reuse of the object variable for any type of Customer object. However, by declaring the derived object using the parent class the derived object only has access to any public properties and methods of the Customer base class and any properties and methods overridden in the EdCustomer class. It does not have access to any public properties and methods of the EdCustomer class, nor to any shadowed properties and methods.
When working with inheritance, there are a few things to remember. First, you are not limited to just one level of inheritance. The inheritance hierarchy can be as deep as needed. The properties and methods are inherited down through the levels. In general, the farther down in the hierarchy a class appears, the more specialized its behavior. For example, you could define a HighSchoolEdCustomer class that inherits from the EdCustomer class that in turn inherits from the Customer class.
Tip To minimize complexity and simplify maintenance, limit your inheritance hierarchies to no more than about four levels.
Each class, however, can inherit from only one parent class. So an EdCustomer class cannot inherit from both a Customer class and an Education class. This is a good thing in that multiple inheritance can get rather complex and make application maintenance very difficult.
There are two primary uses for inheritance in the applications that you develop:
- You have objects of different types that have similar functionality. For example: Educational Customer class and Government Customer class inherit from Customer class.
- You have general processing to be done for sets of objects. For example: every type of business object inherits from a Business Object (BO) class.
You should not use inheritance when:
- You only need one function from the base class. If this is the case, you should delegate to the class instead of inheriting from it.
- If you would need to override all of the functions. If this is the case, you should use an interface instead of implementation inheritance.
- The semantics of the hierarchy are not clear. If there is no clear "is a" type of relationship, such as an educational customer "is a" customer, then delegation or an interface may be a better solution. For example, a vendor has a name like a customer. So the Vendor class could inherit from the Customer class to get the name. However, it is not accurate to say that a vendor "is a" customer. So the semantics are not clear and inheritance should not be used in this case.
Gone are the days when Visual Basic programming meant drawing a few forms and double-clicking on a few buttons to create a complete application. Gone are the days when Visual Basic developers were viewed as "wanna be" developers.
Visual Basic developers are now, and have been, professionals that are well skilled in our trade. Visual Basic .NET has given us a new set of professional development tools; tools on par with those used by other software development professionals; tools that include the intricacies of inheritance and the power of polymorphism. Let's leverage these new OOP features to their full advantage to continue forward as the most effective programmers in the most productive programming language.