http://www.developer.com/

Back to article

The Book of Visual Studio .NET - OOP in VB .NET Crash Course


February 3, 2003

The following is the third of three parts of a chapter on Visual Basic .NET from The Book of Visual Studio .NET, ISBN 1-886411-69-7 from No Starch Press written by Robert B. Dunaway. The first part covered some of the differences between VB .NET from the previous version. The second part presented structured exception handling in Visual Basic .NET. This final part presents OOP concepts in VB .NET.

A   V I S U A L   B A S I C . N E T   
C R A S H   C O U R S E

Object-Oriented Programming (OOP)

As mentioned earlier, Visual Basic did not meet the test as a true object-oriented language that implements true object-oriented programming as defined by abstraction, encapsulation, polymorphism, and inheritance. Visual Basic .NET not only supports inheritance, but also supports a variety of inheritance implementations including interface, forms, and implementation or polymorphism inheritance.

Before you continue any further, let's briefly discuss the four main concepts of object orientation and the implementation code for each. Each brief discussion is followed with an example that demonstrates the discussed OOP concept.

Abstraction

Abstraction is the easiest of the OOP concepts to understand and is often something we implement naturally without realizing it. In short, abstraction is the implementation of code to mimic the actions and characteristics of any realworld entity.

The most commonly-used example for describing abstraction is the abstraction of a person. Imagine that we want to create an object from a class that represents a person. A person class will need to describe its characteristics through the implementation of properties. Actions of the person class are performed by methods.

Encapsulation

Encapsulation is the programmatic equivalent of a black box. An actual black box may have a switch and dials. Inside the box would be the mechanisms to perform the actions provided by the black box.

We expose properties and methods through abstraction, but we implement the actual workings of our component through encapsulation. A few encapsulated actions might include data access, data validation, calculations, adding data to an array or collection, or calling other methods or other components. Exposing our components interface while hiding the component's implementation code effectively separates interface implementation from our black box implementation. This separation helps to modularize components to perform a more specific task while requiring minimal knowledge of how the black box actually works.

One of the more useful applications of encapsulation is in making a complex component. For example, your program may require interaction with a third party system, but interaction with this system can only be achieved through a complex API. Rather than requiring all developers on a project to spend valuable time figuring out how to correctly use the third party API or even find ways to misuse it, one developer could study the API then encapsulate it in a component that exposes a less complex interface. This is a common practice that saves time and reduces potential bugs.

Like abstraction, encapsulation isn't as much a technology as it is a method of code implementation. In the case of encapsulation, our method of implementation separates the exposed interface from the actual implementation code.

Polymorphism

Abstraction is an interface implemented to represent a real-world object; encapsulation is the implementation of a black box through interface and implementation separation; and polymorphism is the ability to implement the interface of another class into multiple classes or to implement multiple interfaces on a single class. This method of implementation is referred to as interface-based programming. A vehicle is a good example of polymorphism. A vehicle interface would only have those properties and methods that all vehicles have, a few of which might include paint color, number of doors, accelerator, and ignition. These properties and methods would apply to all types of vehicles including cars, trucks, and semi-trucks.

Polymorphism will not implement code behind the vehicle's properties and methods. (That's the job of inheritance covered in the next section.) Instead, polymorphism is the implementation of an interface. If the car, truck, and semitruck all implement the same vehicle interface, then the client code for all three classes can be exactly the same.

Implementing the vehicle interface only requires the declaration of properties and methods. To create a new interface, use t he Interface keyword in place of the Class keyword. The client implementing the new interface can do so by using the Implements keyword as shown in the example:

Implements Ivehicle

After using the Implements keyword, you will notice that Intellisense displays the properties and methods of the IVehicle interface. Using the Implements keyword will only give access to the properties and methods of the IVehicle interface; however, you must provide your own code behind the methods and property declarations to match the interface.

Inheritance

Inheritance is the ability to apply another class's interface and code to your own class. Remember, with polymorphism, you got the interface; however, you must apply your own code. The power of inheritance is the ability to inherit code, saving developers time. This type of inheritance is called implementation inheritance. To inherit another class, use the Inherits keyword.

Visual inheritance is the ability to inherit another form's look and feel onto another. Remember, everything in .NET is a class, including forms. If you create a project that exists in the MyApp namespace, create a form name MyBaseForm. The following code will inherit the MyBaseForm within our new form:

Public Class MyNewForm
Inherits MyApp.MyBaseForm
End Class
Properties

Properties are part of a program's interface and describe the characteristics of a class. These properties hold information about a class or, when loaded into memory, an object. Properties, as they exist in classes, are often referred to as "data." When a reference is made to a class's data, you will know that the reference is actually directed toward a class's property.

To create a property, use t he Property keyword and t hen define the type of property you are implementing. Properties can be read-only, write-only, or read and writable. To define the characteristics of properties, use t he keywords ReadOnly, WriteOnly, or supply no definition at all to implement both read and write ability.

Visual Studio .NET makes properties easier to implement by adding the basic shell of property code based on the property's scope definition. Unlike Visual Basic, Visual Basic .NET automatically supplies code for both read and write functionality: "Get" for read ability method and "Set" for write access to a property.

Create a new class, type the following code, and press ENTER:

Public Property FName() As String

Visual Studio .NET will automatically fill in the rest of the code that is required by the FName property:

Get
Return m_FName
End Get
Set(ByVal Value As String)
m_FName = Value
End Set
End Property
Methods

Methods are the actions exposed by a class in the form of either functions or sub-procedures. Sub-procedures and functions both execute code on behalf of the calling application, but sub-procedures simply execute code while functions execute code, then return a value.

The .NET Framework provides at least two new changes to how you can use procedures. In Visual Basic, you could call a procedure without the use of parameters, including procedures that required no parameters at all. The .NET Framework requires parenthesis to follow all methods even when parameters are not required. For example:

  • Visual Basic 6 method call:

    intResult = DoSomething
    
  • Visual Basic .NET method call:

    intResult = DoSomething()
    

Another change is the addition of the Return keyword. When returning a value for a function in Visual Basic, you set the function's name equal to the value being returned. With Visual Basic .NET, you can se t the keyword Return equal to a value and the value will be returned with the function. This is very useful when making code more generic. For instance, you can easily cut and past a method's code without regard to another method's function name because the keyword Return is used for setting the method equal to a return value. Examples of the old versus new method for returning values of a function are:

  • Visual Basic 6 method call:

    Public Function DoSomething() as Int32
    DoSomething = 10
    End Function
    
  • Visual Basic .NET method call:

    Public Function DoSomething() as Int32
    Return = 10
    End Function
    
  • If you look closely at a function's supporting properties you will find that the Return keyword is used by default. You can set the function name equal to a given value.

    The third significant change is in how parameters are passed. Visual Basic passed a parameter value ByRef by default. The preferred method for passing parameter values is to explicitly define whether a value is passed by ByRef or ByVal. Finally, when using the Option keyword, you must define a default value similar to how C has worked for many years now.

    Declaration Options

    We have covered a few of the most common declaration methods including those that describe the scope of a property or method. Several description options will extend or restrict scope.

    Here is a list of the most commonly-used declaration options with brief descriptions of each:

  • Private: The Private keyword defines a variable or method as accessible only by code within the context of where the declaration occurred; outside code is not permitted access. Variables and methods defined as private are often referred to as member variables or methods, and commonly prefixed with an "m".

  • Public: The Public keyword declares a property or method as accessible by anyone within the calling application or within the class itself.

  • Friend: The Friend keyword defines a property or method as accessible by members within the class it is declared in.

  • Protected: The Protected keyword defines a property or method as accessible only by members of its class or by members of an inheriting class.

  • Default: A Default property is a single property of a class that can be set as the default. This allows developers that use your class to work more easily with your default property because they do not need to make a direct reference to the property. Default properties cannot be initialized as Shared or Private and all must be accepted at least on argument or parameter. Default properties do not promote good code readability, so use this option sparingly.

  • Overloads:The Overloads property allows a function to be described using deferent combinations of parameters. Each combination is considered a signature, thereby uniquely defining an instance of the method being defined. You can define a function with multiple signatures without using the keyword Overloads, but if you use the Overloads keyword in one, you must use it in all of the function's Overloaded signatures.

  • Shared:The Shared keyword is used in an inherited or base class to define a property or method as being shared among all instances of a given class. If multiple instances of a class with shared properties or methods are loaded, the shared properties or methods will provide the same data across each instance of the class. When one class alters the value for a shared property, all instances of that class will reflect the change. Shared properties of all instances of the class point to the same memory location.

  • Overridable:The Overridable keyword is used when defining a property or method of an inherited class, as overridable by the inheriting class.

  • Overides: The Overides keyword allows the inheriting class to disregard the property or method of the inherited class and implements its own code.

  • NotOverridable: The NotOverridable keyword explicitly declares a property or method as not overridable by an inheriting class, and all properties are "not overridable" by default. The only real advantage to using this keyword is to make your code more readable.

  • MustOverride: The MustOverride keyword forces the inheriting class to implement its own code for the property or method.

  • Shadows: The Shadows keyword works like the Overloads keyword except that with shadows we do not have to follow rules such as implementing the same signature. The Shadows keyword does not require the consent (override ability) of the inherited class to replace the property or method's implementation code. A method does not have to be defined as overridable for the Shadows keyword to work.

Object Instantiation

When you drag and drop controls onto a Windows Form, you are using objects. When you observe your code, you are looking at a class; when that code is loaded into memory, at runtime, it is considered an object. The importance of the distinction is simply to describe that a class is a template, while an object is an instance of that template in memory. Also, many copies of the template can exist in memory at the same time as objects.

Fortunately, we do not have to depend on the component designer to work with classes; we can build our own classes and components. This is nothing new for a moderately experienced developer; what is new is how Visual Basic .NET permits us to instantiate classes.

Classic COM relied on the Windows Registry to store its exposed properties, methods, events, and enumerations; a client application could only access these exposed interfaces through the Registry. As a result, the way you instantiate classes when using classic COM components in COM+ is very important. Visual Basic .NET accepts a number of instantiation methods without performance impacts, although all variables must first be diminished and then instantiated before they can be used.

The two methods for instantiating classic COM are the CreateObject and New keywords. CreateObject uses the Windows Registry to obtain the interface of the class being instantiated. Because CreateObject depends on windows for access to the register, COM+ can apply a context for use by the COM+ services. The New keyword in classic COM also depends on Windows for access to the Windows Registry. The catch is that it doesn't always have to access the Windows Registry to discover a class's interface if the class resides inside the same component as the calling class. Because the New keyword has no problem accessing a class's interface within the same component, a class can be instantiated by passing COM+ services that would normally add a context or other component service. While this will not prevent you from loading a class into COM+, to take full advantage of COM+ services you should use the CreateObject keyword.

Having said all that, the CreateObject keyword cannot be used to instantiate .NET classes, although it can be used to instantiate classes that exist within classic COM components. Because .NET components don't rely on the Windows Registry, the New keyword is used when loading all .NET components. Here are several examples of how you might define and load classes into memory. First, the variable is diminished as MyClass:

Dim obj As MyClass

Second, you load the class "MyClass" into memory. An instance of a class loaded into memory is referred to as an object. Notice there is no "Set" keyword used.

Obj = New MyClass

Another method is to declare and instanciate an object in a single line:

Dim obj2 As MyClass = New MyClass()

Finally, you can implicitly diminish a variable with a class you are attempting to load. This is the shortest method and is perfectly acceptable:

Dim obj3 As New MyClass

Early and Late Binding

Binding is something we do when diminishing a variable, though many developers may not realize the importance of how they bind a class.

Early binding, often referred to as strong typing, refers to explicitly declaring the class used to define a variable. Early binding has several benefits. For example, when programming, Visual Studio .NET can give access to the class's interface with Intellisense which greatly reduces potential for typos and promotes rapid development. Also, when early binding a class, the Visual Basic compiler can enforce the proper use of a class's interface by providing warnings and refusing to complete the compile until the error is resolved. But performance gains are probably the most important reason to bind early: Early binding allows your program to access your class's interface directly, rather than through the Windows Registry or at runtime. If the compiler knows ahead of time which classes you will be using in your application, it can make the appropriate compilation optimizations.

Late binding can be useful when developing against non-existent components or ones that are being developed. Late binding allows you to continue compiling your code until the component is available; once the class is available, you can modify your code to early bind. You might also use late binding when you truly don't know the object type that will be passed to your function, in which case it is perfectly acceptable to accept any type of object. Before late binding can occur, the Option Strict option must be set to off. Option strict is off by default:

Option Strict Off

To declare a variable as lat e bound, simply diminish t he variable as type

object:
Option Strict Off
Dim obj As Object
'or
Dim obj As System.Object

The System.Object class is the class from which all other classes are derived. While it has no specific characteristics that prevent it from acting as any other class, it is used for late binding.

Components

A class defines something that can exist in memory. It defines an object's interface including properties, methods, events, and enumerations as well as implementation code. An object is an instance of a class in memory; while a class may exist only once, multiple instances of that class may reside in memory as objects. When adding items to a project, you can add a "class" or a "component class". In essence, these are the same thing with one exception: a component class implements the IComponent interface, enabling Visual Studio .NET to drag non-visual controls, such as the timer control, onto a component designer. Visual Studio .NET provides a designer for building components, which allows you to drag visual controls onto your class or component, and begin coding. For example, if you want to program a delay into your class, you can use a component item with the designer to drag the timer control onto your component. The implementation of a graphical designer (IE Component Designer) and container are available to you when you selected "component class" as a new item. Visual Studio creates your class by adding a line of code that inherits everything your class needs to be a component, as follows:

Inherits System.ComponentModel.Component

Then the designer creates a components object using the IContainer interface so that the designer can allow drag and drop capabilities:

Private components As System.ComponentModel.Icontainer

Simple OOP Examples

Now you'll build an example application that employs abstraction, encapsulation, polymorphism, and inheritance:

1. Create a new class library project named "PersonProj".

2. Rename the Class1.vb file to "Person.vb".

3. Add a new Windows Application project to the solution named "TestClient".

4. Right- click on t he TestClient project and select Se t as StartUp Project.

Adding Abstraction

Now add abstraction:

1. Replace the default class in Person.vb with the following code:

Public Class clsPerson
Public FName As String
Public LName As String
Public FullName As String
Public BirthDate As Date
Public Age As Integer
Public TotalHours As Integer
Public Sub Work(ByVal intHours As Integer)
TotalHours += intHours
End Sub
End Class

2. Create a form that looks like Figure 7-2, and name it "frmAbstraction.vb." Use the parameters in Table 7- 4 to build the new Windows Form.

Creating a new Windows Form.

Table 7-4: Form Parameters

Control Property Value

Button Name btnSubmit
Text Submit
Textbox Name txtFName
Textbox Name txtLName
Textbox Name txtFullName
Textbox Name txtBirthDate
Textbox Name txtAge
Textbox Name txtHoursWorked
Label Name lblTotalHoursWorked

3. Right- click on t he TestClient project and select Properties. Change the Startup object to "frmAbstraction.vb".

4. Add a r eference to the PersonProj. Right - click on t he TestClient project and select Add Reference. Select the Projects tab and press the "Select" button then press OK.

5. Add the following object declaration to the initialization of frmAbstraction form:

' Here we are initializing the Person class. Normally this would be done
' when the class was needed for data access but in this case we are using
' the Person class to maintain our data.
Dim objPerson As New PersonProj.clsPerson()

6. Add the following code to the submit buttons click event:

objPerson.FName = txtFName.Text
objPerson.LName = txtLName.Text
objPerson.FullName = txtFullName.Text
If IsDate(txtBirthDate.Text) Then
objPerson.BirthDate = CDate(txtBirthDate.Text)
End If
If IsNumeric(txtAge.Text) Then
objPerson.Age = CInt(txtAge.Text)
End If
If IsNumeric(txtHoursWorked.Text) Then
objPerson.Work(CInt(txtHoursWorked.Text))
End If
lblTotalHoursWorked.Text = "Total Hours Worked: " _
& objPerson.TotalHours.ToString

7. Now run the application.

You should be able to place any value you wish into the First and Last name fields, then completely contradict yourself when filling in your full name. The same should hold true for entering your birth date and age. This example abstracts a person but does not hide any implementation; each time you press the Submit butt on, the Total Hours Worked is summed and displayed.

Adding Encapsulation

The encapsulation example implements the clsPerson class and encapsulated code, hiding the implementation code for all the properties and methods. In the abstraction example, the user has to enter both their birthday and age. As you encapsulate the implementation for the Person object, you will hide the implementation of their age. Age will be derived from the person's birth date, thus preventing a user from creating an invalid age and birth date values. Properties are also encapsulated, allowing the class to derive the full name from the first and last names that have been entered.

1. Add a new Windows Form item named "frmEncapsulation.vb".

2. Add the controls and parameters listed in Table 7- 5 to the frmEncapsulation.vb form.

Table 7-5: Controls for the Form

Control Property Value

Button Name btnSubmit
Text Submit
Textbox Name txtFName
Textbox Name txtLName
Label Name lblFullNameDisplay
Textbox Name txtBirthDate
Label Name lblAgeDisplay
Textbox Name txtHoursWorked
Label Name lblTotalHoursWorked

3. Create a new class in the Person class using the following code:

Public Class clsPerson2
Private m_FName As String
Private m_LName As String
Private m_BirthDate As Date
Private m_TotalHours As Integer
Public Property FName() As String
Get
Return m_FName
End Get
Set(ByVal Value As String)
m_FName = Value
End Set
End Property
Public Property LName() As String
Get
Return m_LName
End Get
Set(ByVal Value As String)
m_LName = Value
End Set
End Property
Public ReadOnly Property FullName() As String
Get
Return m_FName & " " & m_LName
End Get
End Property
Public Property BirthDate() As Date
Get
Return m_BirthDate
End Get
Set(ByVal Value As Date)
If IsDate(Value) Then
m_BirthDate = Value
End If
End Set
End Property
Public ReadOnly Property Age() As Integer
Get
If DatePart(DateInterval.Year, m_BirthDate) = 1 Then
Exit Property
End If
Return DateDiff(DateInterval.Year, m_BirthDate, Now)
End Get
End Property
'Method for adding hours to m_TotalHours worked.
Public Sub Work(ByVal intHours As Integer)
m_TotalHours += intHours
End Sub
Public ReadOnly Property TotalHoursWorked() As Integer
Get
Return m_TotalHours
End Get
End Property
End Class

4. Add the following object declaration to the initialization of frmAbstraction form:

' Here we are initializing the Person class. Normally this would be done
' when the class was needed for data access but in this case we are using
' the Person class to maintain our data.
Dim objPerson As New PersonProj.clsPerson2()

5. Right- click and select Properties then change the StartUp object to "frmEncapsulation".

6. Add the following code to the Submit button:

objPerson.FName = txtFName.Text
objPerson.LName = txtLName.Text
If IsDate(txtBirthDate.Text) Then
objPerson.BirthDate = CDate(txtBirthDate.Text)
Else
MsgBox("Please provide a valid Birth Date.")
End If
If IsNumeric(txtHoursWorked.Text) Then
objPerson.Work(CInt(txtHoursWorked.Text))
txtHoursWorked.Text = ""
End If
lblFullNameDisplay.Text = objPerson.FullName.ToString
lblAgeDisplay.Text = objPerson.Age.ToString
lblTotalHoursWorked.Text = "Total Hours Worked: " _
& objPerson.TotalHoursWorked.ToString

7. Now run the application and enter the information.

You'll notice that your age is calculated for you so that it cannot contradict what the age should be based on the birth date, and the full name is derived from the first and last name.

Adding Polymorphism or Interface-based Inheritance

This example features an interface called IPerson and a class named Employee that uses the IPerson interface:

1. First create a new Windows Form with the same controls as used in the encapsulation example and name it "frmPolymorphism".

2. Add a new Module to the PersonProj project and name it "MyInterfaces.vb".

3. Apply the following code to the MyInterface.vb module. The code defines

the interface.
Public Interface IPerson
Property FName() As String
Property LName() As String
ReadOnly Property FullName() As String
Property BirthDate() As Date
ReadOnly Property Age() As Integer
'Method for adding hours to m_TotalHours worked.
Sub Work(ByVal intHours As Integer)
ReadOnly Property TotalHoursWorked() As Integer
End Interface

4. Right- click and select Properties, then change the StartUp object to "frmPolymorphism".

5. Create a new class to the Person.vb module. You will use it to inherit the new interface:

Public Class clsPerson3
Implements IPerson
Private m_FName As String
Private m_LName As String
Private m_BirthDate As Date
Private m_TotalHours As Integer
Private m_HrRate As Integer
Private m_TotalPay As Double
Public Property FName() As String _
Implements IPerson.FName
Get
Return m_FName
End Get
Set(ByVal Value As String)
m_FName = Value
End Set
End Property
Public Property LName() As String _
Implements IPerson.LName
Get
Return m_LName
End Get
Set(ByVal Value As String)
m_LName = Value
End Set
End Property
Public ReadOnly Property FullName() As String _
Implements IPerson.FullName
Get
Return m_FName & " " & m_LName
End Get
End Property
Public Property BirthDate() As Date _
Implements IPerson.BirthDate
Get
Return m_BirthDate
End Get
Set(ByVal Value As Date)
If IsDate(Value) Then
m_BirthDate = Value
End If
End Set
End Property
Public ReadOnly Property Age() As Integer _
Implements IPerson.Age
Get
If DatePart(DateInterval.Year, _
m_BirthDate) = 1 Then Exit Property
Return DateDiff(DateInterval.Year, _
m_BirthDate, Now)
End Get
End Property
'Method for adding hours to m_TotalHours worked.
Public Sub Work(ByVal intHours As Integer) _
Implements IPerson.Work
m_TotalHours += intHours
End Sub
Public ReadOnly Property TotalHoursWorked() As Integer _
Implements IPerson.TotalHoursWorked
Get
Return m_TotalHours
End Get
End Property
'Additional Properties:
'HrRate
Public WriteOnly Property HrRate()
Set(ByVal Value)
m_HrRate = Value
End Set
End Property
'TotalPay
Public ReadOnly Property TotalPay() As Double
Get
Return m_HrRate * m_TotalHours
End Get
End Property
End Class

6. Now run this example just as you ran the previous ones. You will not notice a difference in how the application runs, although the plumbing has changed quite a bit.

With this simple example, it is easy to question the usefulness of polymorphism. However, if you were to continue building an application that dealt with several aspects of a person, you might find polymorphism more helpful to use if you had to deal with Employees, Customers, Managers, and Contractors.

Working with Inheritance

This inheritance example inherits the interface and implementation code of the clsPerson3 class:

1. Add a new Windows Form item named "frmInheritance.vb". Use the same controls as we used in the encapsulation example.

2. Add the following code to the initialization section of the form:

' Here we are initializing the Person class. Normally this would be done
' when the class was needed for data access but in this case we are using
' the Person class to maintain our data.
Dim objPerson As New Person.clsEmployee()

3. Add the following class to the Person.vb module of the PersonProj project:

Public Class clsEmployee
Inherits clsPerson3
End Class

4. Right- click and select Properties, then change the StartUp object to "frmInheritance".

5. Now Run the inheritance example as you did the previous ones. Notice that the clsEmployee class inherits the functionality of the clsPerson3 class, which in turn implements the IPerson interface. This example demonstrates both polymorphism and inheritance that have been combined to form a single solution.

Summary

In this chapter, you learned that Visual Basic has come a long way from a reduced featured-set language that promoted RAPID application development to a full featured language. Now employing full inheritance, Visual Basic promises to aid in the delivery of enterprise level applications that may previously have been better delivered in another OOP language.


This is the final part of a sample chapter from The Book of Visual Studio .NET, ISBN 1-886411-69-7 from No Starch Press

# # #

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date