Classes and Objects

Generally speaking, a class is a software component that defines and implements one or more interfaces. (Strictly speaking, a class need not implement all the members of an interface. We discuss this later when we talk about abstract members.) In different terms, a class combines data, functions, and types into a new type. Microsoft uses the term type to include classes.

Class Modules in VB.NET

Under Visual Studio.NET, a VB class module is inserted into a project using the Add Class menu item on the Project menu. This inserts a new module containing the code:

Public Class ClassName

End Class

Although Visual Studio stores each class in a separate file, this isn’t a requirement. It is the Class...End Class construct that marks the beginning and end of a class definition. Thus, the code for more than one class as well as one or more code modules (which are similarly delimited by the Module...End Module construct) can be contained in a single source code file.

The CPerson class defined in the next section is an example of a VB class module.

Class Members

In VB.NET, class modules can contain the following types of members:

Data members

This includes member variables (also called fields) and constants.

Event members

Events are procedures that are called automatically by the Common Language Runtime in response to some action that occurs, such as an object being created, a button being clicked, a piece of data being changed, or an object going out of scope.

Function members

This refers to both functions and subroutines. A function member is also called a method. A class’ constructor is a special type of method. We discuss constructors in detail later in this chapter.

Property members

A property member is implemented as a Private member variable together with a special type of VB function that incorporates both accessor functions of the property. We discuss the syntax of this special property function in Section 4.3.5 later in the chapter.

Type members

A class member can be another class, which is then referred to as a nested class.

The following CPerson class illustrates some of the types of members:

Public Class CPerson
    
    ' -------------
    ' Data Members
    ' -------------
    ' Member variables
    Private msName As String
    Private miAge As Integer
    
    ' Member constant 
    Public Const MAXAGE As Short = 120
    
    ' Member event
    Public Event Testing(  )
    
    ' ----------------
    ' Function Members
    ' ----------------
    
    ' Method 
    Public Sub Test(  )
        RaiseEvent Testing(  )
    End Sub
    
    Property Age(  ) As Integer
        Get
            Age = miAge
        End Get
        Set(ByVal Value As Integer)
            ' Some validation
            If Value < 0 Then
                MsgBox("Age cannot be negative.")
            Else
                miAge = Value
            End If
        End Set
    End Property

    ' Property
    Property Name(  ) As String
        ' Accessors for the property
        Get
            Name = msName
        End Get
        Set(ByVal Value As String)
            msName = Value
        End Set
    End Property
    
   ' Overloaded constructor
    Overloads Sub New(  )

    End Sub

    ' Constructor that initializes name
    Overloads Sub New(ByVal sNewName As String)
        msName = sNewName
    End Sub
    
    Sub Dispose(  )
        ' Code here to clean up
    End Sub

End 
Class

The Public Interface of a VB.NET Class

We have seen that, when speaking in general object-oriented terms, the exposed members of a software component constitute the component’s public interface (or just interface). Now, in VB.NET, each member of a class module has an access type, which may be Public, Private, Friend, Protected, or Protected Friend. We discuss each of these in detail later in this chapter. Suffice it to say, a VB.NET class module may accordingly have Public, Private, Friend, Protected, and Protected Friend members.

Thus, we face some ambiguity in defining the concept of the public interface of a VB.NET class. The spirit of the term might indicate that we should consider any member that is exposed outside of the class itself as part of the public interface of the class. This would include the Protected, Friend, and Protected Friend members, as well as the Public members. On the other hand, some might argue that the members of the public interface must be exposed outside of the project in which the class resides, in which case only the Public members would be included in the interface. Fortunately, we need not make too much fuss over the issue of what exactly constitutes a VB.NET class’ public interface, as long as we remain aware that the term may be used differently by different people.

Objects

A class is just a description of some properties and methods and does not have a life of its own (with the exception of shared members, which we discuss later). In general, to execute the methods and use the properties of a class, we must create an instance of the class, officially known as an object. Creating an instance of a class is referred to as instancing, or instantiating, theclass.

There are three ways to instantiate an object of a VB.NET class. One method is to declare a variable of the class’ type:

Dim APerson As CPerson

and then instantiate the object using the New keyword as follows:

APerson = New CPerson(  )

We can combine these two steps as follows:

Dim APerson As New CPerson(  )

or:

Dim APerson As CPerson = New CPerson(  )

The first syntax is considered shorthand for the second.

Properties

Properties are members that can be implemented in two different ways. In its simplest implementation, a property is just a public variable, as in:

Public Class CPerson
    
    Public Age As Integer
    
End Class

The problem with this implementation of the Age property is that it violates the principle of encapsulation; anyone who has access to a CPerson object can set its Age property to any Integer value, even negative integers, which are not valid ages. In short, there is no opportunity for data validation. (Moreover, this implementation of a property does not permit its inclusion in the public interface of the class, as we have defined that term.)

The “proper” object-oriented way to implement a property is to use a Private data member along with a special pair of function members. The Private data member holds the property value; the pair of function members, called accessors, are used to get and set the property value. This promotes data encapsulation, since we can restrict access to the property via code in the accessor functions, which can contain code to validate the data. The following code implements the Age property:

Private miAge As Integer

Property Age(  ) As Integer
    Get
        Age = miAge
    End Get
    Set(ByVal Value As Integer)
        ' Some validation
        If Value < 0 Then
            MsgBox("Age cannot be negative.")
        Else
            miAge = Value
        End If
    End Set
End Property

As you can see from the previous code, VB has a special syntax for defining the property accessors. As soon as we finish typing the line:

Property Age(  ) As Integer

the VB IDE automatically creates the following template:

Property Age(  ) As Integer
    Get

    End Get
    Set(ByVal Value As Integer)

    End Set
End Property

Note the Value parameter that provides access to the incoming value. Thus, if we write:

Dim cp As New CPerson(  )
cp.Age = 20

then VB passes the value 20 into the Property procedure in the Value argument.

Instance and Shared Members

The members of a class fall into two categories:

Instance members

Members that can only be accessed through an instance of the class, that is, through an object of the class. To put it another way, instance members “belong” to an individual object rather than to the class as a whole.

Shared (static) members

Members that can be accessed without creating an instance of the class. These members are shared among all instances of the class. More correctly, they are independent of any particular object of the class. To put it another way, shared members “belong” to the class as a whole, rather than to its individual objects or instances.

Instance members are accessed by qualifying the member name with the object’s name. Here is an example:

Dim APerson As New CPerson(  )
APerson.Age = 50

To access a shared member, we simply qualify the member with the class name. For instance, the String class in the System namespace of the .NET Framework Class Library has a shared method called Compare that compares two strings. Its syntax (in one form) is:

Public Shared Function Compare(String, String) As Integer

This function returns 0 if the strings are equal, -1 if the first string is less than the second, and 1 if the first string is greater than the second. Since the method is shared, we can write:

Dim s As String = "steve"
Dim t As String = "donna"
MsgBox(String.Compare(s, t))   ' Displays 1

Note the way the Compare method is qualified with the name of the String class.

Shared members are useful for keeping track of data that is independent of any particular instance of the class. For instance, suppose we want to keep track of the number of CPerson objects in existence at any given time. Then we write code such as the following:

' Declare a Private shared variable to hold the instance count
Private Shared miInstanceCount As Integer

' Increment the count in the constructor
' (If there are additional constructors, 
' this code must be added to all of them.)
Sub new(  )
    miInstanceCount += 1
End Sub 

' Supply a function to retrieve the instance count
Shared Function GetInstanceCount(  ) As Integer
    Return miInstanceCount
End Function

' Decrement the count in the destructor
Overrides Protected Sub Finalize(  )
   miInstanceCount -= 1
   MyBase.Finalize
End Sub

Now, code such as the following accesses the shared variable:

Dim steve As New CPerson(  )
MsgBox(CPerson.GetInstanceCount)      ' Displays 1
Dim donna As New CPerson(  )
MsgBox(CPerson.GetInstanceCount)      ' Displays 2

Class Constructors

When an object of a particular class is created, the compiler calls a special function called the class’ constructor or instance constructor. Constructors can be used to initialize an object when necessary. (Constructors take the place of the Class_ Initialize event in earlier versions of VB.)

We can define constructors in a class module. However, if we choose not to define a constructor, VB uses a default constructor. For instance, the line:

Dim APerson As CPerson = New CPerson(  )

invokes the default constructor of our CPerson class simply because we have not defined a custom constructor.

To define a custom constructor, we just define a subroutine named New within the class module. For instance, suppose we want to set the Name property to a specified value when a CPerson object is first created. Then we can add the following code to the CPerson class:

' Custom constructor
Sub New(ByVal sName As String)
    Me.Name = sName
End Sub

Now we can create a CPerson object and set its name as follows:

Dim APerson As CPerson = New CPerson("fred")

or:

Dim APerson As New CPerson("fred")

Note that because VB.NET supports function overloading (discussed later in this chapter), we can define multiple constructors in a single class, provided each constructor has a unique argument signature. We can then invoke any of the custom constructors simply by supplying the correct number and type of arguments for that constructor.

Note also that once we define one or more custom constructors, we can no longer invoke the default (that is, parameterless) constructor with a statement such as:

Dim APerson As New CPerson(  )

Instead, to call a parameterless constructor, we must specifically add the constructor to the class module:

' Default constructor
Sub New(  )
    ...
End Sub

Finalize, Dispose, and Garbage Collection

In VB 6, a programmer can implement the Class_Terminate event to perform any clean up procedures before an object is destroyed. For instance, if an object held a reference to an open file, it might be important to close the file before destroying the object itself.

In VB.NET, the Terminate event no longer exists, and things are handled quite differently. To understand the issues involved, we must first discuss garbage collection.

When the garbage collector determines that an object is no longer needed (which it does, for instance, when the running program no longer holds a reference to the object), it automatically runs a special destructor method called Finalize. However, it is important to understand that, unlike with the Class_Terminate event, we have no way to determine exactly when the garbage collector will call the Finalize method. We can only be sure that it will be called at some time after the last reference to the object is released. Any delay is due to the fact that the .NET Framework uses a system called reference-tracing garbage collection, which periodically releases unused resources.

Finalize is a Protected method. That is, it can be called from a class and its derived classes, but it is not callable from outside the class, including by clients of the class. (In fact, since the Finalize destructor is automatically called by the garbage collector, a class should never call its own Finalize method directly.) If a class’ Finalize method is present, then it should explicitly call its base class’ Finalize method as well. Hence, the general syntax and format of the Finalize method is:

Overrides Protected Sub Finalize(  )
   ' Cleanup code goes here
   MyBase.Finalize
End Sub

The benefits of garbage collection are that it is automatic and it ensures that unused resources are always released without any specific interaction on the part of the programmer. However, it has the disadvantages that garbage collection cannot be initiated directly by application code and some resources may remain in use longer than necessary. Thus, in simple terms, we cannot destroy objects on cue.

We should note that not all resources are managed by the Common Language Runtime. These resources, such as Windows handles and database connections, are thus not subject to garbage collection without specifically including code to release the resources within the Finalize method. But, as we have seen, this approach does not allow us or clients of our class to release resources on demand. For this purpose, the Framework Class Library defines a second destructor called Dispose. Its general syntax and usage is:

Class classname
   Implements IDisposable

Public Sub Dispose(  ) Implements IDisposable.Dispose
   ' cleanup code goes here
   ' call child objects' Dispose methods, if necessary, here
End Sub

' Other class code 

End Class

Note that classes that support this callable destructor must implement the IDisposable interface — hence the Implements statement just shown. IDisposable has just one member, the Dispose method.

It is important to note that it is necessary to inform any clients of the class that they must call this method specifically in order to release resources. (The technical term for this is the manual approach!)

Get VB.NET Language in a Nutshell, Second Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.