Chapter 26. Classes and Structures

A variable holds a single value. It may be a simple value such as an Integer or String, or a reference that points to a more complex entity. Two kinds of more complex entities are classes and structures.

Classes and structures are both container types. They group several related data values into a convenient package that you can manipulate as a group.

For example, an EmployeeInfo structure might contain fields that hold information about an employee (such as first name, last name, employee ID, office number, extension, and so on). If you make an EmployeeInfo structure and fill it with the data for a particular employee, you can then move the structure around as a single unit instead of passing around a bunch of separate variables holding the first name, last name, and the rest.

This chapter explains how to declare classes and structures, and how to create instances of them (instantiate them). It explains the differences between classes and structures and provides some advice about which to use under different circumstances.

Finally, this chapter describes some of the mechanical issues that you'll face when building classes. It explains how garbage collection affects objects. It finishes by explaining how to implement some of the most basic features of classes: constants, properties, methods, and events.

CLASSES

A class packages data and related behavior. For example, a WorkOrder class might store data describing a customer's work order in its properties. It could contain methods (subroutines and functions) for manipulating the work order. It might provide methods for scheduling the work, modifying the order's requirements, and setting the order's priority.

Here is the syntax for declaring a class:

[attribute_list] [Partial] [accessibility] [Shadows] [inheritance] _
 Class name[(Of type_list)]
      [Inherits parent_class]
      [Implements interface]
      statements
End Class

The only things that all class declarations must include are the Class clause (including the class's name) and the End Class statement. Everything else is optional. The following code describes a valid (albeit not very interesting) class:

Class EmptyClass
End Class

The following sections describe the pieces of the general declaration in detail.

Attribute_list

The optional attribute_list is a comma-separated list of attributes that apply to the class. An attribute further refines the definition of a class to give more information to the compiler and the runtime system.

Attributes are rather specialized. They address issues that arise when you perform very specific programming tasks. For example, if your application must use drag-and-drop support to copy instances of the class from one application to another, you must mark the class as serializable, as shown in the following code:

<Serializable()>
Class Employee
     Public FirstName As String
     Public LastName As String
End Class

Some attributes are particular to specific kinds of classes. For example, the DefaultEvent attribute gives the form designer extra information about component classes. If you double-click a component on a form, the code designer opens to the component's default event.

Because attributes are so specialized, they are not described in more detail here. For more information, see the sections in the online help that are related to the tasks you need to perform.

For more information on attributes, see Microsoft's "Attributes in Visual Basic" web page at msdn.microsoft.com/39967861.aspx. For a list of attributes that you can use, go to Microsoft's "Attribute Class" web page at msdn.microsoft.com/system.attribute.aspx and look at the "Inheritance Hierarchy" section.

Partial

The Partial keyword tells Visual Basic that the current declaration defines only part of the class. The following code shows the Employee class broken into two pieces:

Partial Public Class Employee
    Public FirstName As String
    Public LastName As String
    ...
End Class

... other code, possibly unrelated to the Employee class ...

Partial Public Class Employee
    Public Email As String
    ...
End Class

The program could contain any number of other pieces of the Employee class, possibly in different code modules. At compile time, Visual Basic finds these pieces and combines them to define the class.

One of the primary benefits of classes is that they hold the code and data associated with the class together in a nice package. Scattering the pieces of a class in this way makes the package less self-contained and may lead to confusion. To prevent confusion, you should avoid splitting a class unless you have a good reason to (for example, to allow different developers to work on different pieces of the class at the same time or if one piece must have Option Strict turned off).

At least one of the pieces of the class must be declared with the Partial keyword, but in the other pieces it is optional. Explicitly providing the keyword in all of the class's partial definitions emphasizes the fact that the class is broken into pieces and may minimize confusion.

Accessibility

A class's accessibility clause can take one of the following values: Public, Protected, Friend, Protected Friend, or Private.

Public indicates that the class should be available to all code inside or outside of the class's module. This enables the most access to the class. Any code can create and manipulate instances of the class.

You can use the Protected keyword only if the class you are declaring is contained inside another class. For example, the following code defines an Employee class that contains a protected EmployeeAddress class:

Public Class Employee
    Public FirstName As String
    Public LastName As String
    Protected Address As EmployeeAddress

    Protected Class EmployeeAddress
        Public Street As String
        Public City As String
        Public State As String
        Public Zip As String
    End Class

    ... other code ...
End Class

Because the EmployeeAddress class is declared with the Protected keyword, it is visible only within the enclosing Employee class and any derived classes. For example, if the Manager class inherits from the Employee class, code within the Manager class can access the Address variable.

The Friend keyword indicates that the class should be available to all code inside or outside of the class's module within the same project. The difference between this and Public is that Public allows code outside of the project to access the class. This is generally only an issue for code libraries (.dll files) and control libraries. For example, suppose that you build a code library containing dozens of routines and then you write a program that uses the library. If the library declares a class with the Public keyword, the code in the library and the code in the main program can use the class. If the library declares a class with the Friend keyword, only the code in the library can access the class, not the code in the main program.

Protected Friend is the union of the Protected and Friend keywords. A class declared Protected Friend is accessible only to code within the enclosing class or a derived class and only within the same project.

A class declared Private is accessible only to code in the enclosing module, class, or structure. If the EmployeeAddress class were declared Private, only code within the Employee class could use that class.

If you do not specify an accessibility level, it defaults to Friend.

Shadows

The Shadows keyword indicates that the class hides the definition of some other entity in the enclosing class's base class.

The following code shows an Employee class that declares a public class OfficeInfo and defines an instance of that class named Office. The derived class Manager inherits from Employee. It declares a new version of the OfficeInfo class with the Shadows keyword. It defines an instance of this class named ManagerOffice.

Public Class Employee
    Public Class OfficeInfo
        Public OfficeNumber As String
        Public Extension As String
    End Class

    Public FirstName As String
    Public LastName As String
    Public Office As New OfficeInfo
End Class

Public Class Manager
    Inherits Employee

    Public Shadows Class OfficeInfo
    Public OfficeNumber As String
    Public Extension As String
    Public SecretaryOfficeNumber As String
    Public SecretaryExtension As String
    End Class

    Public ManagerOffice As New OfficeInfo
End Class

The following code uses the Employee and Manager classes. It creates instances of the two classes and sets their Office.Extension properties. Both of those values are part of the Employee class's version of the OfficeInfo class. Next, the code sets the Manager object's ManagerOffice.SecretaryExtension value.

Dim emp As New Employee
Dim mgr As New Manager
emp.Office.Extension = "1111"
mgr.Office.Extension = "2222"
mgr.ManagerOffice.SecretaryExtension = "3333"

Note that the Manager class contains two different objects of type OfficeInfo. Its Office property is the Employee class's flavor of OfficeInfo class. Its ManagerOffice value is the Manager class's version of OfficeInfo.

The presence of these different classes with the same name can be confusing. Usually, you are better off not using the Shadows keyword in the declarations and giving the classes different names. In this case, you could call the Manager class's included class ManagerOfficeInfo.

Inheritance

A class's inheritance clause can take the value MustInherit or NotInheritable.

MustInherit prohibits the program from creating instances of the class. The program should create an instance of a derived class instead. This kind of class is sometimes called an abstract class.

By using MustInherit, you can make a parent class that defines some of the behavior that should be implemented by derived classes without implementing the functionality itself. The parent class is not intended to be used itself, just to help define the derived classes.

For example, the following code defines the Vehicle class with the MustInherit keyword. This class defines features that are common to all vehicles. It defines a NumWheels variable and a Drive subroutine declared with the MustOverride keyword. The real world doesn't contain generic vehicles, however. Instead, it contains cars, trucks, and other specific kinds of vehicles. The code defines a Car class that inherits from the Vehicle class. When you enter the Inherits statement and press Enter, Visual Basic automatically adds the empty Drive subroutine required by the Vehicle class.

Public MustInherit Class Vehicle
    Public NumWheels As Integer
    Public MustOverride Sub Drive()
End Class

Public Class Car
    Inherits Vehicle

    Public Overrides Sub Drive()

    End Sub
End Class

The following code uses these classes. It declares a Vehicle and a Car variable. The first assignment statement causes an error because it tries to make a new Vehicle object. This is not allowed, because Vehicle is declared with the MustInherit keyword. The program sets variable a_car to a new Car variable and then sets variable a_vehicle to a_car. This works because a Car is a type of Vehicle, so the a_vehicle variable can refer to a Car object. In its last line, the code assigns a_vehicle directly to a new Car object.

Dim a_vehicle As Vehicle
Dim a_car As Car

a_vehicle = New vehicle ' Error. Vehicle is MustInherit.
a_car = New Car         ' This works.
a_vehicle = a_car       ' This works.
a_vehicle = New Car     ' This works.

The NotInheritable keyword does the opposite of the MustInherit keyword. MustInherit says that a class must be inherited to be instantiated. NotInheritable says no class can inherit from this one.

You can use NotInheritable to stop other developers from making new versions of the classes you have built. This isn't really necessary if you design a well-defined object model before you start programming and if everyone obeys it. NotInheritable can prevent unnecessary proliferation of classes if developers don't pay attention, however. For example, declaring the Car class NotInheritable would prevent overeager developers from deriving FrontWheelDriveCar, RedCar, and Subaru classes from the Car class.

Of type_list

The Of type_list clause makes the class generic. It allows the program to create instances of the class that work with specific data types. For example, the following code defines a generic Tree class. The class includes a public variable named RootObject that has the data type given in the class's Of data_type clause.

Public Class Tree(Of data_type)
    Public RootObject As data_type
    ...
End Class

When you read this declaration, you should think "Tree of something," where something is defined later when you make an instance of the class.

The following code fragment declares the variable my_tree to be a "Tree of Employee." It then instantiates my_tree and sets its RootObject variable to an Employee object.

Dim my_tree As Tree(Of Employee)
my_tree = New Tree(Of Employee)
my_tree.RootObject = New Employee
...

Chapter 29, "Generics," discusses generic classes further.

Inherits parent_class

The Inherits statement indicates that the class (the child class) is derived from another class (the parent class). The child class automatically inherits the parent's properties, methods, and events.

The following code defines an Employee class that contains LastName, FirstName, OfficeNumber, and Phone variables. It then derives the Manager class from the Employee class. Because it inherits from the Employee class, the Manager class automatically has LastName, FirstName, OfficeNumber, and Phone variables. It also adds new SecretaryOfficeNumber and SecretaryPhone variables. These are available to instances of the Manager class but not to the Employee class.

Public Class Employee
    Public FirstName As String
    Public LastName As String
    Public OfficeNumber As String
    Public Phone As String
End Class

Public Class Manager
    Inherits Employee

    Public SecretaryOfficeNumber As String
    Public SecretaryPhone As String
End Class

If a class inherits from another class, the Inherits statement must be the first statement after the Class statement that is not blank or a comment. Also note that a class can inherit from at most one parent class, so a class definition can include at most one Inherits statement.

For more information on inheritance, see the section "Inheritance" in Chapter 25.

Implements interface

The Implements keyword indicates that a class will implement an interface. An interface defines behaviors that the implementing class must provide, but it does not provide any implementation for the behaviors.

For example, the following code defines the IDomicile interface. By convention, the names of interfaces should begin with the capital letter I. This interface defines the SquareFeet, NumBedrooms, and NumBathrooms properties, and the Clean subroutine.

Public Interface IDomicile
    Property SquareFeet() As Integer
    Property NumBedrooms() As Integer
    Property NumBathrooms() As Integer
    ReadOnly Property NeedsFireSystem() As Boolean
    Sub Clean()
End Interface

The interface also defines the read-only property NeedsFireSystem. Usually, a read-only property calculates its return value from other property values. For example, NeedsFireSystem might return True if a house has more than a certain number of square feet.

The House class shown in the following code implements the IDomicile interface. When you type the Implements statement and press Enter, Visual Basic automatically generates empty routines to provide the features defined by the interface.

Public Class House
    Implements IDomicile

    Public Sub Clean() Implements IDomicile.Clean

    End Sub

    Public Property NumBathrooms() As Integer Implements IDomicile.NumBathrooms
        Get

        End Get
        Set(ByVal Value As Integer)

        End Set
    End Property

    Public Property NumBedrooms() As Integer Implements IDomicile.NumBedrooms
        Get

        End Get
        Set(ByVal Value As Integer)

        End Set
End Property

    Public Property SquareFeet() As Integer Implements IDomicile.SquareFeet
        Get

        End Get
        Set(ByVal Value As Integer)

        End Set
    End Property

    Public ReadOnly Property NeedsFireSystem() As Boolean _
     Implements IDomicile.NeedsFireSystem
        Get

        End Get
    End Property
End Class

An interface defines behaviors but does not supply them. When you derive a class from a parent class, the derived class inherits all the code that the parent class uses to implement its features. When you implement an interface, the behavior is defined, but not supplied for you. That makes interfaces more difficult to use than inheritance, so inheritance is generally preferred whenever it is possible.

One case where the inheritance of Visual Basic is insufficient is when you need to implement multiple inheritance. In multiple inheritance, one child class can inherit from more than one parent class. For example, you might define a Domicile class and a Boat class, and then make the HouseBoat class inherit from both. You can do this in some languages but not in Visual Basic. However, you can make a class implement more than one interface. Simply define IDomicile and IBoat interfaces and then have the HouseBoat class implement them both.

The IDomicile interface was shown earlier. The following code defines the IBoat interface:

Public Interface IBoat
    Property HasMotor() As Boolean
    Property HorsePower() As Integer
    Property Capacity() As Integer
    Sub Sail()
End Interface

The following code shows parts of the HouseBoat class. It begins with two Implements statements that indicate that the class implements both the IDomicile and IBoat interfaces. It then includes implementations of the methods specified by IDomicile and IBoat.

Public Class HouseBoat
    Implements IDomicile
    Implements IBoat

    Public Sub Clean() Implements IDomicile.Clean

    End Sub

    Public ReadOnly Property NeedsFireSystem() As Boolean Implements _
     IDomicile.NeedsFireSystem
        Get

        End Get
    End Property

    Public Property NumBathrooms() As Integer Implements IDomicile.NumBathrooms
        Get

        End Get
        Set(ByVal Value As Integer)

        End Set
    End Property

    Public Property NumBedrooms() As Integer Implements IDomicile.NumBedrooms
        Get

        End Get
        Set(ByVal Value As Integer)

        End Set
    End Property

    Public Property SquareFeet() As Integer Implements IDomicile.SquareFeet
        Get

        End Get
        Set(ByVal Value As Integer)

        End Set
    End Property

    Public Property Capacity() As Integer Implements IBoat.Capacity
        Get

        End Get
        Set(ByVal Value As Integer)

        End Set
    End Property

    Public Property HasMotor() As Boolean Implements IBoat.HasMotor
        Get
End Get
        Set(ByVal Value As Boolean)

        End Set
    End Property

    Public Property HorsePower() As Integer Implements IBoat.HorsePower
        Get

        End Get
        Set(ByVal Value As Integer)

        End Set
    End Property

    Public Sub Sail() Implements IBoat.Sail

    End Sub
End Class

Using an interface in place of inheritance is sometimes called interface inheritance. The class doesn't inherit a parent class's code, but it does inherit the definition of the features that it must provide.

Note that a class can inherit from one class and also implement one or more interfaces. To save coding, you could make one of the parent interfaces into a class. For example, if the IDomicile interface defines more behaviors than the IBoat interface, and if those behaviors are generic enough to provide help for derived classes, you can turn IDomicile into a Domicile class that provides those features. Then the HouseBoat class could inherit the Domicile class's features and implement the IBoat interface.

If a class declaration uses any Implements statements, they must come after any Inherits statement and before any other statements (other than blank lines and comments).

For more information on interfaces and how you can use them to mimic inheritance, see the section "Interface Inheritance" in Chapter 25.

STRUCTURES

Structures are very similar to classes. The syntax for declaring a structure is as follows:

[attribute_list] [Partial] [accessibility] [Shadows] _
Structure name[(Of type_list)]
    [Implements interface]
    Statements
End Structure

The only thing that all structure declarations must include is the Structure clause (including the structure's name) and the End Structure statement. The rest is optional.

Unlike a class, however, a structure cannot be empty. It must contain at least one variable or event declaration. The following code describes a valid structure. Its only member is a Private variable, so this structure wouldn't be of much use, but it is valid.

Structure EmptyStructure
     Private m_Num As Integer
End Structure

The structure's attribute_list and accessibility clauses, Shadows and Partial keywords, and the Implements statement are the same as those for classes. See the earlier sections discussing these keywords for details.

There are two main differences between a structure and a class: structures cannot inherit and structures are value types rather than reference types.

Structures Cannot Inherit

Unlike a class, a structure cannot inherit so it cannot use the MustInherit, NotInheritable, or Inherits keywords; however, like a class, a structure can implement any number of interfaces. You can use interface inheritance to define inheritance-mimicking hierarchies of structures, and you can simulate multiple inheritance by making a structure implement multiple interfaces.

Structures Are Value Types

The biggest difference between a structure and a class is in how each allocates memory for its data. Classes are reference types. That means an instance of a class is actually a reference to the object's storage in memory. When you create an instance of a class, Visual Basic actually creates a reference that points to the object's actual location in memory.

On the other hand, structures are value types. An instance of a structure contains the data inside the structure rather than simply pointing to it. Figure 26-1 illustrates the difference.

A structure contains the data, whereas a class object contains a reference that points to data.

Figure 26.1. A structure contains the data, whereas a class object contains a reference that points to data.

The difference between reference and value type has several important consequences that are described in the following sections.

Memory Required

The difference in memory required by classes and structures is small when you consider only a single object. If you look at an array, however, the distinction is more important. An array of class objects contains references to data in some other part of memory. When you first declare the array, the references all have the value Nothing, so they don't point to any data and no memory is allocated for the data. The references take 4 bytes each, so the array uses only 4 bytes per array entry.

An array of structure instances, on the other hand, allocates space for the data inside the array. If each structure object takes up 1000 bytes of memory, then an array containing N items uses 1000 * N bytes of memory. Each structure object's memory is allocated, whether or not its fields contain meaningful data.

Figure 26-2 illustrates this situation. The array of class objects on the left uses very little memory when the references are Nothing. The array of structure objects on the right uses a lot of memory even if its elements have not been initialized.

An array of class objects contains small references to data, many of which may be Nothing. An array of structures takes up a significant amount of memory.

Figure 26.2. An array of class objects contains small references to data, many of which may be Nothing. An array of structures takes up a significant amount of memory.

If you must use a large array of objects where only a few at a time will have values other than Nothing, using a class may save the program a considerable amount of memory. If you will need most of the objects to have values other than Nothing at the same time, it may be faster to allocate all the memory at once using a structure. This will also use slightly less memory, because an array of class references requires 4 extra bytes per entry to hold the references.

Heap and Stack Performance

Visual Basic programs allocate variables from two pools of memory called the stack and the heap. They take memory for value types (such as integers and dates) from the stack.

Space for reference types comes from the heap. More than one reference can point to the same chunk of memory allocated on the heap. That makes garbage collection and other heap-management issues more complex than using the stack, so using the heap is generally slower than using the stack.

Because structures are value types and classes are reference types, structures are allocated on the stack and class objects are allocated from the heap. That makes structures faster than classes. The exact difference for a particular program depends on the application.

Note that arrays are themselves reference types, so all arrays are allocated from the heap whether they contain structures or references to class objects. The memory for an array of structures is allocated all at once, however, so there is still some benefit to using structures. All the memory in an array of structures is contiguous, so the program can access its elements more quickly than it would if the memory were scattered throughout the heap.

Object Assignment

When you assign one reference type variable to another, you make a new reference to an existing object. When you are finished, the two variables point to the same object. If you change the object's fields using one variable, the fields shown by the other are also changed.

On the other hand, if you set one value type variable equal to another, Visual Basic copies the data from one to the other. If you change the fields in one object, the fields in the other remain unchanged. Figure 26-3 illustrates the difference for classes and structures.

Assigning one class reference to another makes them both point to the same object. Assigning one structure variable to another makes a new copy of the data.

Figure 26.3. Assigning one class reference to another makes them both point to the same object. Assigning one structure variable to another makes a new copy of the data.

Example program StructuresAndClasses uses the following code to demonstrate this difference:

Dim cperson1 As New CPerson
Dim cperson2 As CPerson
cperson1.FirstName = "Alice"
cperson2 = cperson1
cperson2.FirstName = "Ben"
MessageBox.Show(cperson1.FirstName & vbCrLf & cperson2.FirstName)

Dim sperson1 As New SPerson
Dim sperson2 As SPerson
sperson1.FirstName = "Alice"
sperson2 = sperson1
sperson2.FirstName = "Ben"
MessageBox.Show(sperson1.FirstName & vbCrLf & sperson2.FirstName)
                                                  
Assigning one class reference to another makes them both point to the same object. Assigning one structure variable to another makes a new copy of the data.

The code creates a CPerson object and sets its first name value. It then assigns another CPerson variable to the same object. Because CPerson is a class, the two variables refer to the same piece of memory so when the code sets the new variable's first name value it overwrites the previous variable's first name value. The message box displays the name Ben twice.

The code performs the same steps again but this time it uses structure variables instead of class variables. The code makes an SPerson structure and sets its first name value. When it sets the second SPerson variable equal to the first one, it makes a copy of the structure. Now when the code sets the second variable's first name to Ben, it does not overwrite the previous variable's first name value. The message box displays the names Alice and Ben.

Parameter Passing

When you pass a parameter to a function or subroutine, you can pass it by reference using the ByRef keyword, or by value using the ByVal keyword. If you pass a parameter by reference, any changes that the routine makes are reflected in the original parameter passed into the routine.

For example, consider the following code. Subroutine TestByRef creates an integer named i and sets its value to 1. It then calls subroutine PassByVal. That routine declares its parameter with the ByVal keyword, so i is passed by value. PassByVal multiplies its parameter by 2 and ends. Because the parameter was declared ByVal, the original variable i is unchanged, so the message box displays the value 1. Next the program calls subroutine PassByRef, passing it the variable i. Subroutine PassByRef declares its parameter with the ByRef keyword, so a reference to the variable is passed into the routine. PassByRef doubles its parameter and ends. Because the parameter is declared with the ByRef keyword, the value of variable i is modified so the message box displays the value 2.

Public Sub TestByRef()
Dim i As Integer = 1

    PassByVal(i)
    MessageBox.Show(i.ToString) ' i = 1.

    PassByRef(i)
    MessageBox.Show(i.ToString) ' i = 2.
End Sub

Public Sub PassByVal(ByVal the_value As Integer)
   the_value *= 2
End Sub

Public Sub PassByRef(ByRef the_value As Integer)
   the_value *= 2
End Sub

When you work with class references and structures, you must think a bit harder about how ByRef and ByVal work. There are four possible combinations: reference ByVal, structure ByVal, reference ByRef, and structure ByRef.

If you pass a class reference to a routine by value, the routine receives a copy of the reference. If it changes the reference (perhaps making it point to a new object), the original reference passed into the routine remains unchanged. It still points to the same object it did when it was passed to the routine. However, the routine can change the values in the object to which the reference points. If the reference points to a Person object, the routine can change the object's FirstName, LastName, and other fields. It cannot change the reference itself to make it point to a different Person object, but it can change the object's data.

On the other hand, suppose that you pass a structure into a routine by value. In that case, the routine receives a copy of the entire structure. The routine can change the values contained in its copy of the structure, but the original structure's values remain unchanged. It cannot change the original structure's fields the way it could if the parameter were a reference type.

If you pass a class reference variable by reference, the routine can not only modify the values in the rg106eference's object but it can also make the reference point to a different object. For example, the routine could use the New keyword to make the variable point to a completely new object.

If you pass a structure by reference, the routine receives a pointer to the structure's data. If it changes the structure's data, the fields in the original variable passed into the routine are modified.

In addition to these differences in behavior, passing class references and structures by reference or by value can make differences in performance. When you pass a reference to data, Visual Basic only needs to send the routine a 4-byte value. If you pass a structure into a routine by value, Visual Basic must duplicate the entire structure, so the routine can use its own copy. If the structure is very large, that may take a little extra time.

Boxing and Unboxing

Visual Basic allows a program to treat any variable as an object. For example, a collection class stores objects. If you add a simple value type such as an Integer to a collection, Visual Basic wraps the Integer in an object and adds that object to the collection.

The process of wrapping the Integer in an object is called boxing. Later, if you need to use the Integer as a value type again, the program unboxes it. Because structures are value types, the program must box and unbox them whenever it treats them as objects, and that adds some extra overhead.

Some operations that require boxing and possibly unboxing include assigning a structure to an Object variable, passing a structure to a routine that takes an Object as a parameter, or adding a structure to a collection class. Note that this last operation includes adding a structure to a collection used by a control or other object. For example, adding a structure to a ListBox control's Items collection requires boxing.

Note that arrays are themselves reference types, so treating an array as an object doesn't require boxing.

CLASS INSTANTIATION DETAILS

When you declare a reference variable, Visual Basic allocates space for the reference. Initially, that reference is set to Nothing, so it doesn't point to anything and no memory is allocated for an actual object.

You create an object by using the New keyword. Creating an actual object is called instantiating the class.

The following code shows a simple object declaration and instantiation. The first line declares the reference variable. The second line makes the variable point to a new Employee object.

Dim emp As Employee ' Declare a reference to an Employee object.
emp = New Employee  ' Make a new Employee object and make emp point to it.

Visual Basic also enables you to declare and initialize a variable in a single statement. The following code shows how to declare and initialize an object reference in one statement:

Dim emp As Employee = New Employee ' Declare and instantiate an object.

Visual Basic lets you declare a variable to be of a new object type, as shown in the following statement. This version has the same effect as the preceding one but is slightly more compact.

Dim emp As New Employee ' Declare and instantiate an object.

Both of these versions that define and initialize an object in a single statement ensure that the variable is initialized right away. They guarantee that the object is instantiated before you try to use it. If you place these kinds of declarations immediately before the code where the object is used, they also make it easy to see where the object is defined.

Although you can declare and instantiate a reference variable separately, value type variables are allocated when they are declared. Because structures are value types, when you declare one you also allocate space for its data, so you don't need to use the New keyword to initialize a structure variable.

Both classes and structures can provide special subroutines called constructors. A constructor is a special subroutine named New that Visual Basic calls when a new instance of the class or structure is created. The constructor can perform initialization tasks to get the new object ready for use.

A constructor can optionally take parameters to help in initializing the object. For example, the Person class shown in the following code has a constructor that takes as parameters first and last names and saves them in the control's FirstName and LastName variables:

Public Class Person
   Public FirstName As String
   Public LastName As String

   Public Sub New(ByVal first_name As String, ByVal last_name As String)
      FirstName = first_name
      LastName = last_name
   End Sub
End Class

The following code shows how a program might use this constructor to create a new Person object:

Dim author As New Person("Rod", "Stephens")

You can overload the New method just as you can overload other class methods. The different overloaded versions of the constructor must have different parameter lists so that Visual Basic can decide which one to use when it creates a new object.

The following code shows a Person class that provides two constructors. The first takes no parameters and sets the object's FirstName and LastName values to <unknown>. The second version takes two strings as parameters and copies them into the object's FirstName and LastName values.

Public Class Person
    Public FirstName As String
    Public LastName As String

    Public Sub New()
        Me.New("<unknown>", "<unknown>")
    End Sub

    Public Sub New(ByVal first_name As String, ByVal last_name As String)
        FirstName = first_name
        LastName = last_name
    End Sub
End Class

The following code uses each of these constructors:

Dim person1 As New Person                    ' <unknown> <unknown>.
Dim person2 As New Person("Olga", "O'Toole") ' Olga O'Toole.

If you do not provide any constructors for a class, Visual Basic allows the program to use the New keyword with no parameters. If you create any constructor, however, Visual Basic does not allow the program to use this default empty constructor (without parameters) unless you build one explicitly. For example, if the previous version of the Person class did not include an empty constructor, the program could not use the first declaration in the previous code that doesn't include any parameters.

You can use this feature to ensure that the program assigns required values to an object. In this case, it would mean that the program could not create a Person object without assigning FirstName and LastName values.

If you want to allow an empty constructor in addition to other constructors, an alternative is to create a single constructor with optional parameters. The following code shows this approach. With this class, the program could create a new Person object, passing its constructor zero, one, or two parameters.

Public Class Person
    Public FirstName As String
    Public LastName As String

    Public Sub New(
     Optional ByVal first_name As String = "<unknown>",
     Optional ByVal last_name As String = "<unknown>")
        FirstName = first_name
LastName = last_name
    End Sub
End Class

When you use a class's empty constructor to create an object, you can also include a With clause to initialize the object's properties. The following code uses the Person class's parameter-less constructor to make a new Person object. The With statement then sets values for the object's FirstName and LastName values.

Dim author As New Person() With {.FirstName = "Rod", .LastName = "Stephens"}

STRUCTURE INSTANTIATION DETAILS

Structures handle instantiation somewhat differently from object references. When you declare a reference variable, Visual Basic does not automatically allocate the object to which the variable points. In contrast, when you declare a value type such as a structure, Visual Basic automatically allocates space for the variable's data. That means you never need to use the New keyword to instantiate a structure.

However, the Visual Basic compiler warns you if you do not explicitly initialize a structure variable before using it. To satisfy the compiler, you can use the New keyword to initialize the variable when you declare it.

A structure can also provide constructors, and you can use those constructors to initialize the structure. The following code defines the SPerson structure and gives it a constructor that takes two parameters, the second optional:

Public Structure SPerson
    Public FirstName As String
    Public LastName As String

    Public Sub New(
     ByVal first_name As String,
     Optional ByVal last_name As String = "<unknown>")
        FirstName = first_name
        LastName = last_name
    End Sub
End Structure

To use a structure's constructor, you initialize the structure with the New keyword much as you initialize a reference variable. The following code allocates an SPerson structure variable using the two-parameter constructor:

Dim artist As New SPerson("Sergio", "Aragones")

You can also use structure constructors later to reinitialize a variable or set its values, as shown here:

' Allocate the artist variable.
Dim artist As SPerson

' Do something with artist.
...

' Reset FirstName and LastName to Nothing.
artist = New SPerson
...

' Set FirstName and LastName to Bill Amend.
artist = New SPerson("Bill", "Amend")

As is the case with classes, you can use a With clause to set structure values when you initialize a structure variable. For example, the following code creates a new SPerson structure and sets its FirstName and LastName values:

Dim artist As New SPerson() With {.FirstName = "Anna", .LastName = "Aux"}

Structure and class constructors are very similar, but there are some major differences.

  • A structure cannot declare a constructor that takes no parameters.

  • A structure cannot provide a constructor with all optional parameters, because that would allow the program to call it with no parameters.

  • Visual Basic always allows the program to use a default empty constructor to declare a structure variable, but you cannot make it use your empty constructor. Unfortunately, that means you cannot use a default constructor to guarantee that the program always initializes the structure's values as you can with a class. If you need that feature, you should use a class instead of a structure.

  • You also cannot provide initialization values for variables declared within a structure as you can with a class. That means you cannot use this technique to provide default values for the structure's variables.

The following code demonstrates these differences. The CPerson class defines initial values for its FirstName and LastName variables, provides an empty constructor, and provides a two-parameter constructor. The SPerson structure cannot define initial values for FirstName and LastName and cannot provide an empty constructor.

' Class.
Public Class CPerson
   Public FirstName As String = "<unknown>" ' Initialization value allowed.
   Public LastName As String = "<unknown>"  ' Initialization value allowed.

   ' Empty constructor allowed.
   Public Sub New()
   End Sub

   ' Two-parameter constructor allowed.
   Public Sub New(ByVal first_name As String, ByVal last_name As String)
       FirstName = first_name
       LastName = last_name
   End Sub
End Class

' Structure.
Public Structure SPerson
   Public FirstName As String ' = "<unknown>" ' Initialization NOT allowed.
   Public LastName As String ' = "<unknown>" ' Initialization NOT allowed.

    '' Empty constructor NOT allowed.
    'Public Sub New()
    'End Sub

    ' Two-parameter constructor allowed.
    Public Sub New(ByVal first_name As String, ByVal last_name As String)
       FirstName = first_name
       LastName = last_name
    End Sub
End Structure

GARBAGE COLLECTION

When a program starts, the system allocates a chunk of memory for the program called the managed heap. When it allocates data for reference types (class objects), Visual Basic uses memory from this heap. (For more information about the stack and heap and their relative performance, see the section "Heap and Stack Performance" earlier in this chapter.)

When the program no longer needs to use a reference object, Visual Basic does not mark the heap memory as free for later use. If you set a reference variable to Nothing so that no variable points to the object, the object's memory is no longer available to the program, but Visual Basic does not reuse the object's heap memory, at least not right away.

The optimizing engine of the garbage collector determines when it needs to clean up the heap. If the program allocates and frees many reference objects, a lot of the heap may be full of memory that is no longer used. In that case, the garbage collector will decide to clean house.

When it runs, the garbage collector examines all the program's reference variables, parameters that are object references, CPU registers, and other items that might point to heap objects. It uses those values to build a graph describing the heap memory that the program can still access. It then compacts the objects in the heap and updates the program's references so they can find any moved items. The garbage collector then updates the heap itself so that the program can allocate memory from the unused portion.

When it destroys an object, the garbage collector frees the object's memory and any managed resources it contains. It may not free unmanaged resources, however. You can determine when and how an object frees its managed and unmanaged resources by using the Finalize and Dispose methods.

Finalize

When it destroys an object, the garbage collector frees any managed resources used by that object. For example, suppose that an unused object contains a reference to an open file stream. When the garbage collector runs, it notices that the file stream is inaccessible to the program, so it destroys the file stream as well as the object that contains its reference.

However, suppose that the object uses an unmanaged resource that is outside of the scope of objects that Visual Basic understands. For example, suppose the object holds an integer representing a file handle, network connection, or channel to a hardware device that Visual Basic doesn't understand. In that case, the garbage collector doesn't know how to free that resource.

You can tell the garbage collector what to do by overriding the class's Finalize method, which is inherited from the Object class. The garbage collector calls an object's Finalize method before permanently removing the object from the heap. Note that there are no guarantees about exactly when the garbage collector calls this method, or the order in which different objects' methods are called. Two objects' Finalize methods may be called in either order even if one contains a reference to the other or if one was freed long before the other. If you must guarantee a specific order, you must provide more specific clean-up methods of your own.

Example program GarbageCollection uses the following code to demonstrate the Finalize method:

Public Class Form1
    Public Running As Boolean

    Private Class Junk
        Public MyForm As Form1

        Public Sub New(ByVal my_form As Form1)
            MyForm = my_form
        End Sub

        ' Garbage collection started.
        Protected Overrides Sub Finalize()
            ' Stop making objects.
            MyForm.Running = False
End Sub
    End Class

    ' Make objects until garbage collection starts.
    Private Sub btnCreateObjects_Click() Handles btnCreateObjects.Click
        Running = True

        Dim new_obj As Junk
        Dim max_i As Long
        For i As Long = 1 To 100000
            new_obj = New Junk(Me)

            If Not Running Then
                max_i = i
                Exit For
            End If
        Next i
        MessageBox.Show("Allocated " & max_i.ToString & " objects")
    End Sub
End Class
                                                  
Finalize

The Form1 class defines the public variable Running. It then defines the Junk class, which contains a variable referring to the Form1 class. This class's constructor saves a reference to the Form1 object that created it. Its Finalize method sets the Form1 object's Running value to False.

When the user clicks the form's Create Objects button, the btnCreateObjects_Click event handler sets Running to True and starts creating Junk objects, passing the constructor this form as a parameter. The routine keeps creating new objects as long as Running is True. Note that each time it creates a new object, the old object that the variable new_obj used to point to becomes inaccessible to the program so it is available for garbage collection.

Eventually the program's heap runs low, so the garbage collector executes. When it destroys one of the Junk objects, the object's Finalize subroutine executes and sets the form's Running value to False. When the garbage collector finishes, the btnCreateObjects_Click event handler sees that Running is False, so it stops creating new Junk objects. It displays the number of the last Junk object it created and is done.

In one test, this program created 30,456 Junk objects before the garbage collector ran. In a second trial run immediately after the first, the program created 59,150 objects, and in a third it created 26,191. The garbage collector gives you little control over when it finalizes objects.

Visual Basic also calls every object's Finalize method when the program ends. Again, there are no guarantees about the exact timing or order of the calls to different objects' Finalize methods.

Example program FinalizeObjects, which is available for download on the book's web site, uses the following code to test the Finalize method when the program ends:

Public Class Form1
    Private Class Numbered
        Private m_Number As Integer
        Public Sub New(ByVal my_number As Integer)
            m_Number = my_number
        End Sub

        ' Garbage collection started.
        Protected Overrides Sub Finalize()
            ' Display the object's number.
            Debug.WriteLine("Finalized object " & m_Number)
        End Sub
    End Class

    ' Make objects until garbage collection starts.
    Private Sub btnGo_Click() Handles btnGo.Click
        Static i As Integer = 0
        i += 1
        Dim new_numbered As New Numbered(i)
        Debug.WriteLine("Created object " & i.ToString)
    End Sub
End Class
                                                  
Finalize

The Numbered class contains a variable m_Number and initializes that value in its constructor. Its Finalize method writes the object's number in the Output window.

The btnGo_Click event handler creates a new Numbered object, giving it a new number. When the event handler ends, the new_numbered variable referring to the Numbered object goes out of scope, so the object is no longer available to the program. If you look at the Output window at this time, you will probably find that the program has not bothered to finalize the object yet. If you click the button several times and then close the application, Visual Basic calls each object's Finalize method. If you click the button five times, you should see five messages displayed by the objects' Finalize methods.

If your class allocates unmanaged resources, you should give it a Finalize method to free them.

Dispose

Because Visual Basic doesn't keep track of whether an object is reachable at any given moment, it doesn't know when it can permanently destroy an object until the program ends or the garbage collector reclaims it. That means the object's memory and resources may remain unused for quite a while. The memory itself isn't a big issue. If the program's heap runs out of space, the garbage collector runs to reclaim some of the unused memory.

If the object contains a reference to a resource, however, that resource is not freed until the object is finalized. That can have dire consequences. You generally don't want control of a file, network connection, scanner, or other scarce system resource left to the whims of the garbage collector.

By convention, the Dispose subroutine frees an object's resources. Before a program frees an object that contains important resources, it can call that object's Dispose method to free the resources explicitly.

To handle the case where the program does not call Dispose, the class should also free any unmanaged resources that it holds in its Finalize subroutine. Because Finalize is executed whether or not the program calls Dispose, it must also be able to execute both the Dispose and Finalize subroutines without harm. For example, if the program shuts down some piece of unusual hardware, it probably should not shut down the device twice.

To make building a Dispose method a little easier, Visual Basic defines the IDisposable interface, which declares the Dispose method. If you enter the statement Implements IDisposable and press Enter, Visual Basic creates an empty Dispose method for you.

Example program UseDispose, which is available for download on the book's web site, uses the following code to demonstrate the Dispose and Finalize methods:

Public Class Form1
    Private Class Named
        Implements IDisposable

        ' Save our name.
        Public Name As String
        Public Sub New(ByVal new_name As String)
            Name = new_name
        End Sub

        ' Free resources.
        Protected Overrides Sub Finalize()
            Dispose()
        End Sub

        ' Display our name.
        Public Sub Dispose() Implements System.IDisposable.Dispose
            Static done_before As Boolean = False
            If done_before Then Exit Sub
            done_before = True

            Debug.WriteLine(Name)
        End Sub
    End Class

    ' Make an object and dispose it.
    Private Sub btnDispose_Click() Handles btnDispose.Click
        Static i As Integer = 0
        i += 1
        Dim obj As New Named("Dispose " & i)
obj.Dispose()
    End Sub

    ' Make an object and do not dispose it.
    Private Sub btnNoDispose_Click() Handles btnNoDispose.Click
        Static i As Integer = 0
        i += 1
        Dim obj As New Named("No Dispose " & i)
    End Sub
End Class
                                                  
Dispose

The Named class has a Name variable that contains a string identifying an object. Its Finalize method simply calls its Dispose method. Dispose uses a static variable named done_before to ensure that it performs its task only once. If it has not already run, the Dispose method displays the object's name. In a real application, this method would free whatever resources the object holds. Whether the program explicitly calls Dispose, or whether the garbage collector calls the object's Finalize method, this code is executed exactly once.

The main program has two buttons labeled Dispose and No Dispose. When you click the Dispose button, the btnDispose_Click event handler makes a Named object, giving it a new name, and then calls the object's Dispose method, which immediately displays the object's name.

When you click the No Dispose button, the btnNoDispose_Click event handler makes a new Named object with a new name and then ends without calling the object's Dispose method. Later, when the garbage collector runs or when the program ends, the object's Finalize method executes and calls Dispose, which displays the object's name.

If your class allocates managed or unmanaged resources and you don't want to wait for the garbage collector to get around to freeing them, you should implement a Dispose method and use it when you no longer need an object.

CONSTANTS, PROPERTIES, AND METHODS

Declaring constants, properties, and methods within a class is the same as declaring them outside a class. The main difference is that the context of the declaration is the class rather than a namespace. For example, a variable declared Private within a class is available only to code within the class.

For information on declaring variables and constants, see Chapter 15, "Data Types, Variables, and Constants." For information on declaring methods, see Chapter 17, "Subroutines and Functions," which also describes property procedures, special routines that implement a property for a class.

One issue that is sometimes confusing is that the unit scope of a class is the class's code, not the code within a specific instance of the class. If you declare a variable within a class Private, then all code within the class can access the variable, whether or not that code belongs to the instance of the object that contains the variable.

For example, consider the following Student class. The m_Scores array is Private to the class, so you might think that a Student object could only access its own scores. In fact, any Student object can access any other Student object's m_Scores array as well. The CompareToStudent subroutine calculates the total score for the current Student object. It then calculates the total score for another student and displays the results.

Public Class Student
    Public FirstName As String
    Public LastName As String
    Private m_Scores() As Integer
    ...
    Public Sub CompareToStudent(ByVal other_student As Student)
        Dim my_score As Integer = 0
        For i As Integer = 0 To m_Scores.GetUpperBound(0)
            my_score += m_Scores(i)
        Next i

        Dim other_score As Integer = 0
        For i As Integer = 0 To other_student.m_Scores.GetUpperBound(0)
            other_score += other_student.m_Scores(i)
        Next i

        Debug.WriteLine("My score: " & my_score)
        Debug.WriteLine("Other score: " & other_score)
    End Sub
    ...
End Class

Breaking the encapsulation provided by the objects in this way can lead to unnecessary confusion. It is generally better to try to access an object's Private data only from within that object. You can provide access routines that make using the object's data easier.

The following version of the Student class includes a TotalScore function that returns the total of a Student object's scores. This function works only with its own object's scores, so it does not pry into another object's data. The CompareToStudent subroutine uses the TotalScore function to display the total score for its object and for a comparison object.

Public Class Student
    Public FirstName As String
    Public LastName As String
    Private m_Scores() As Integer
    ...
    Public Sub CompareToStudent(ByVal other_student As Student)
        Debug.WriteLine("My score: " & TotalScore())
        Debug.WriteLine("Other score: " & other_student.TotalScore())
    End Sub

    ' Return the total of this student's scores.
    Private Function TotalScore() As Integer
        Dim total_score As Integer = 0
        For i As Integer = 0 To m_Scores.GetUpperBound(0)
            total_score += m_Scores(i)
        Next i
Return total_score
    End Function
    ...
End Class

Function TotalScore is itself declared Private, so only code within the class can use it. In this example, the CompareToStudent subroutine calls another object's Private TotalScore function, so the separation between the two objects is not absolute, but at least CompareToStudent doesn't need to look directly at the other object's data.

EVENTS

Properties let the application view and modify an object's data. Methods let the program invoke the object's behaviors and perform actions. Together, properties and methods let the program send information (data values or commands) to the object.

In a sense, events do the reverse: They let the object send information to the program. When something noteworthy occurs in the object's code, it can raise an event to tell the main program about it. The main program can then decide what to do about the event.

The following sections describe events. They explain how a class declares events and how other parts of the program can catch events.

Declaring Events

A class object can raise events whenever it needs to notify to the program of changing circumstances. Normally, the class declares the event using the Event keyword. The following text shows the Event statement's syntax:

[attribute_list] [accessibility] [Shadows] _
Event event_name([parameters]) [Implements interface.event]

The following sections describe the pieces of this declaration. Some of these are similar to earlier sections that describe constant, variable, and class declarations. By now, you should notice some familiarity in the use of the attribute_list and accessibility clauses. For more information on constant and variable declarations, see Chapter 15. For more information on class declarations, refer to the section "Classes" earlier in this chapter.

attribute_list

The attribute_list defines attributes that apply to the event. For example, the following declaration defines a description that the code editor should display for the ScoreAdded event:

Imports System.ComponentModel

Public Class Student
<Description("Occurs when a score is added to the object")>
    Public Event ScoreAdded(ByVal test_number As Integer)
    ...
End Class

accessibility

The accessibility value can take one of the following values: Public, Protected, Friend, Protected Friend, or Private. These values determine which pieces of code can catch the event.

The meanings of these keywords is very similar to that of the class accessibility keywords described earlier in this chapter. See the subsection "Accessibility" inside the "Classes" section earlier in this chapter for details.

Shadows

The Shadows keyword indicates that this event replaces an event in the parent class that has the same name but not necessarily the same parameters.

parameters

The parameters clause gives the parameters that the event will pass to event handlers. The syntax for the parameter list is the same as the syntax for declaring the parameter list for a subroutine or function.

If an event declares a parameter with the ByRef keyword, the code that catches the event can modify that parameter's value. When the event handler ends, the class code that raised the event can read the new parameter value.

Implements interface.event

If the class implements an interface and the interface defines an event, this clause identifies this event as the one defined by the interface. For example, the IStudent interface shown in the following code defines the ScoreChanged event handler. The Student class implements the IStudent interface. The declaration of the ScoreChanged event handler uses the Implements keyword to indicate that this event handler provides the event handler defined by the IStudent interface.

Public Interface IStudent
    Event ScoreChanged()
    ...

End Interface

Public Class Student
    Implements IStudent

    Public Event ScoreChanged() Implements IStudent.ScoreChanged
    ...
End Class

Raising Events

After it has declared an event, a class raises it with the RaiseEvent keyword. It should pass the event whatever parameters were defined in the Event statement.

For example, the Student class shown in the following code declares a ScoreChange event. Its AddScore method makes room for a new score, adds the score to the Scores array, and then raises the ScoreChanged event, passing the event handler the index of the score in the Scores array.

Public Class Student
    Private Scores() As Integer
    ...
    Public Event ScoreChanged(ByVal test_number As Integer)
    ...
    Public Sub AddScore(ByVal new_score As Integer)
        ReDim Preserve Scores(Scores.Length)
        Scores(Scores.Length - 1) = new_score
        RaiseEvent ScoreChanged(Scores.Length - 1)
    End Sub
    ...
End Class

Catching Events

You can catch an object's events in two ways. First, you can declare the object variable using the WithEvents keyword, as shown in the following code:

Private WithEvents TopStudent As Student

In the code editor, click the left drop-down list and select the variable's name. In the right drop-down list, select the event. This makes the code editor create an empty event handler similar to the following one. When the object raises its ScoreChanged event, the event handler executes.

Private Sub TopStudent_ScoreChanged(ByVal test_number As Integer) _
 Handles TopStudent.ScoreChanged

End Sub

The second method for catching events is to use the AddHandler statement to define an event handler for the event. First, write the event handler subroutine. This subroutine must take parameters of the proper type to match those defined by the event's declaration in the class. The following code shows a subroutine that can handle the ScoreChanged event. Note that the parameter's name has been changed, but its accessibility (ByRef or ByVal) and data type must match those declared for the ScoreChanged event.

Private Sub HandleScoreChanged(ByVal quiz_num As Integer)

End Sub

If the event handler's parameter list is long and complicated, writing an event handler can be tedious. To make this easier, you can declare an object using the WithEvents keyword and use the drop-down lists to give it an event handler. Then you can edit the event handler to suit your needs (change its name, remove the Handles clause, change parameter names, and so forth).

After you build the event handler routine, use the AddHandler statement to assign the routine to a particular object's event. The following statement makes the HandleScoreChanged event handler catch the TopStudent object's ScoreChanged event:

AddHandler TopStudent.ScoreChanged, AddressOf HandleScoreChanged

Using AddHandler is particularly handy when you want to use the same event handler with more than one object. For example, you might write an event handler that validates a TextBox control's contents to ensure that it contains a valid phone number. By repeatedly using the AddHandler statement, you can make the same event handler validate any number of TextBox controls.

AddHandler is also convenient if you want to work with an array of objects. The following code shows how a program might create an array of Student objects and then use the HandleScoreChanged subroutine to catch the ScoreChanged event for all of them:

' Create an array of Student objects.
Const MAX_STUDENT As Integer = 30
Dim students(MAX_STUDENT) As Student
For i As Integer = 0 To MAX_STUDENT
    students(i) = New Student
Next i

' Add ScoreChanged event handlers.
For i As Integer = 0 To MAX_STUDENT
    AddHandler students(i).ScoreChanged, AddressOf HandleScoreChanged
Next i
...

If you plan to use AddHandler in this way, you may want to ensure that the events provide enough information for the event handler to figure out which object raised the event. For example, you might modify the ScoreChanged event so that it passes a reference to the object raising the event into the event handler. Then the shared event handler can determine which Student object had a score change.

If you add an event handler with AddHandler, you can later remove it with the RemoveHandler statement. The syntax is the same as the syntax for AddHandler, as shown here:

RemoveHandler TopStudent.ScoreChanged, AddressOf HandleScoreChanged

Note that relaxed delegates allow an event handler to declare its parameters to have different data types from those provided by the event, as long as the new data types are compatible, or to omit the parameters entirely.

For example, suppose the Student class defines a ScoreChanged event that takes an Integer parameter. The following three subroutines could all catch this event. The first matches the event's parameters precisely. The second version declares its quiz_num parameter to be a Long. Long is compatible with Integer so, when it invokes the event handler, Visual Basic can convert the Integer value into a Long parameter safely. The third version of the event handler declares no parameters so the event's Integer value is ignored.

Private Sub HandleScoreChanged1(ByVal quiz_num As Integer)

End Sub

Private Sub HandleScoreChanged2(ByVal quiz_num As Long)

End Sub

Private Sub HandleScoreChanged3()

End Sub

For more information, see the section "Relaxed Delegates" in Chapter 17.

Declaring Custom Events

A second form of event declaration provides more control over the event. This version is quite a bit more complicated and at first can seem very confusing. Skim through the syntax and description that follow and then look at the example. Then if you go back and look at the syntax and description again, they should make more sense. This version is also more advanced and you may not need it often (if ever), so you can skip it for now if you get bogged down.

This version enables you to define routines that are executed when the event is bound to an event handler, removed from an event handler, and called. The syntax is as follows:

[attribute_list] [accessibility] [Shadows] _
 Custom Event event_name As delegate_name [Implements interface.event]
    [attribute_list] AddHandler(ByVal value As delegate_name)
        ...
    End AddHandler
[attribute_list] RemoveHandler(ByVal value As delegate_name)
        ...
    End RemoveHandler
    [attribute_list] RaiseEvent(delegate_signature)
        ...
    End RaiseEvent
End Event

The attribute_list, accessibility, Shadows, and Implements interface.event parts have the same meaning as in the previous, simpler event declaration. See the section, "Declaring Events," earlier in this chapter for information on these pieces.

The delegate_name tells Visual Basic the type of event handler that will catch the event. For example, the delegate might indicate a subroutine that takes as a parameter a String variable named new_name. The following code shows a simple delegate for this routine. The delegate's name is NameChangedDelegate. It takes a String parameter named new_name.

Public Delegate Sub NameChangedDelegate(ByVal new_name As String)

For more information on delegates, see the section "Delegates" in Chapter 15.

The main body of the custom event declaration defines three routines named AddHandler, RemoveHandler, and RaiseEvent. You can use these three routines to keep track of the event handlers assigned to an object (remember that the event declaration is declaring an event for a class) and to call the event handlers when appropriate.

The AddHandler routine executes when the program adds an event handler to the object. It takes as a parameter a delegate variable named value. This is a reference to a routine that matches the delegate defined for the event handler. For example, if the main program uses the AddHandler statement to add the subroutine Employee_NameChanged as an event handler for this object, the parameter to AddHandler is a reference to the Employee_NameChanged subroutine.

Normally, the AddHandler subroutine saves the delegate in some sort of collection so the RaiseEvent subroutine described shortly can invoke it.

The RemoveHandler subroutine executes when the program removes an event handler from the object. It takes as a parameter a delegate variable indicating the event handler that should be removed. Normally, the RemoveHandler subroutine deletes the delegate from the collection that AddHandler used to originally store the delegate.

Finally, the RaiseEvent subroutine executes when the object's code uses the RaiseEvent statement to raise the event. For example, suppose that the Employee class defines the NameChanged event. When the class's FirstName or LastName property procedure changes an Employee object's name, it uses the RaiseEvent statement to raise the NameChanged event. At that point, the custom RaiseEvent subroutine executes.

Normally, the RaiseEvent subroutine calls the delegates stored by the AddHandler subroutine in the class's collection of event delegates.

Example program CustomEvent, which is available for download on the book's web site, uses the following code to implement a custom NameChanged event. It begins with the FirstName and LastName property procedures. The Property Set procedures both use the RaiseEvent statement to raise the NameChanged event. This is fairly straightforward and works just as it would if the class used the simpler event declaration.

Public Class Employee
    ' The FirstName property.
    Private m_FirstName As String
    Public Property FirstName() As String
        Get
            Return m_FirstName
        End Get
        Set(ByVal value As String)
            m_FirstName = value
            RaiseEvent NameChanged(m_FirstName & " " & m_LastName)
        End Set
    End Property

    ' The LastName property.
    Private m_LastName As String
    Public Property LastName() As String
        Get
            Return m_LastName
        End Get
        Set(ByVal value As String)
            m_LastName = value
            RaiseEvent NameChanged(m_FirstName & " " & m_LastName)
        End Set
    End Property

    ' List to hold the event handler delegates.
    Private m_EventDelegates As New ArrayList

    ' Defines the event handler signature.
    Public Delegate Sub NameChangedDelegate(ByVal new_name As String)

    ' Define the custom NameChanged event.
    Public Custom Event NameChanged As NameChangedDelegate
        AddHandler(ByVal value As NameChangedDelegate)
            Debug.WriteLine("AddHandler")
            m_EventDelegates.Add(value)
        End AddHandler

        RemoveHandler(ByVal value As NameChangedDelegate)
            Debug.WriteLine("RemoveHandler")
            m_EventDelegates.Remove(value)
        End RemoveHandler

        RaiseEvent(ByVal new_name As String)
            Debug.WriteLine("RaiseEvent (" & new_name & ")")
            For Each a_delegate As NameChangedDelegate In m_EventDelegates
                a_delegate(new_name.Replace(" ", "+"))
            Next a_delegate
End RaiseEvent
    End Event
End Class
                                                  
Declaring Custom Events

Next, the code defines an ArrayList named m_EventDelegates that it will use to store the event handler delegates. It then uses a Delegate statement to define the types of event handlers that this event will call. In this example, the event handler must be a subroutine that takes a String parameter passed by value.

Now, the code defines the NameChanged custom event. Notice that the Custom Event statement ends with the delegate NameChangedDelegate. If you type this first line and press Enter, Visual Basic creates empty AddHandler, RemoveHandler, and RaiseEvent subroutines for you.

Subroutine AddHandler displays a message and saves the delegate in the m_EventDelegates list. When AddHandler is called, the value parameter refers to an event handler routine that has the proper type.

The subroutine RemoveHandler displays a message and removes a delegate from the m_EventDelegates list. In a real application, this routine would need some error-handling code in case the delegate is not in m_EventDelegates.

When the FirstName and LastName property set procedures use the RaiseEvent statement, the RaiseEvent subroutine executes. This routine's parameter takes whatever value the class used when it used the RaiseEvent statement. This subroutine displays a message and then loops through all the delegates stored in the m_EventDelegates list, invoking each. It passes each delegate the new_name value it received in its parameter, with spaces replaced by plus signs.

The following code demonstrates the NameChanged event handler. It creates a new Employee object and then uses two AddHandler statements to assign the event Employee_NameChanged handler to the object's NameChanged event. This makes the custom AddHandler subroutine execute twice and save two references to the Employee_NameChanged subroutine in the delegate list.

Next, the program sets the Employee object's FirstName. The FirstName property set procedure raises the NameChanged event so the RaiseEvent subroutine executes. RaiseEvent loops through the delegate list and calls the delegates. In this example, that means the subroutine Employee_NameChanged executes twice.

The program then uses a RemoveHandler statement to remove an Employee_NameChanged event handler. The custom RemoveHandler subroutine executes and removes one instance of the Employee_NameChanged subroutine from the delegate list. Next the program sets the Employee object's LastName. The LastName property set procedure uses the RaiseEvent statement so the RaiseEvent subroutine executes. Now there is only one instance of the Employee_NameChanged subroutine in the delegate list, so it is called once. Finally, the code uses the RemoveHandler statement to remove the remaining instance of Employee_NameChanged from the delegate list. The RemoveHandler subroutine executes and removes the instance from the delegate list.

Dim emp As New Employee
AddHandler emp.NameChanged, AddressOf Employee_NameChanged
AddHandler emp.NameChanged, AddressOf Employee_NameChanged
emp.FirstName = "Rod"
RemoveHandler emp.NameChanged, AddressOf Employee_NameChanged
emp.LastName = "Stephens"
RemoveHandler emp.NameChanged, AddressOf Employee_NameChanged

                                                  
Declaring Custom Events

The following text shows the result in the Debug window. It shows where the AddHandler, RaiseEvent, and RemoveHandler subroutines execute. You can also see where the Employee_NameChanged event handler executes and displays its name.

AddHandler
AddHandler
RaiseEvent (Rod )
Employee_NameChanged: Rod+
Employee_NameChanged: Rod+
RemoveHandler
RaiseEvent (Rod Stephens)
Employee_NameChanged: Rod+Stephens
RemoveHandler

Shared Variables

If you declare a variable in a class with the Shared keyword, all objects of the class share a single instance of that variable. You can get or set the variable's value through any instance of the class.

For example, suppose the Student class declares a shared NumStudents variable, as shown in the following code:

Public Class Student
    Shared NumStudents As Integer
    ...
End Class

In this case, all instances of the Student class share the same NumStudents value. The following code creates two Student objects. It uses one to set the shared NumStudents value and uses the other to display the result.

Dim student1 As New Student
Dim student2 As New Student
student1.NumStudents = 100
MessageBox.Show(student2.NumStudents)

Because all instances of the class share the same variable, any changes to the value that you make using one object are visible to all the others. Figure 26-4 illustrates this idea. Each Student class instance has its own FirstName, LastName, Scores, and other individual data values, but they all share the same NumStudents value.

If a variable in a class is declared Shared, all instances of a class share the same value.

Figure 26.4. If a variable in a class is declared Shared, all instances of a class share the same value.

Because a shared variable is associated with the class as a whole and not a specific instance of the class, Visual Basic lets you refer to it using the class's name in addition to using specific instance variables. In fact, if you try to access a shared variable through a specific instance rather than through the class, Visual Basic flags the code with a warning, although it will run the code.

The following code defines a new Student object and uses it to set NumStudents to 100. It then uses the class name to display the NumStudents value.

Dim student1 As New Student
student1.NumStudents = 100
MessageBox.Show(Student.NumStudents)

Shared Methods

Shared methods are a little less intuitive than shared variables. Like shared variables, shared methods are accessible using the class's name. For example, the NewStudent function shown in the following code is declared with the Shared keyword. This function creates a new Student object, initializes it by adding it to some sort of database, and then returns the new object.

Public Class Student
    ...
    ' Return a new Student.
    Public Shared Function NewStudent() As Student
        ' Instantiate the Student.
        Dim new_student As New Student

        ' Add the new student to the database.
        ' ...

        ' Return the new student.
        Return new_student
    End Function
    ...
End Class

This type of function that creates a new instance of a class is sometimes called a factory method. In some cases, you can use an appropriate constructor instead of a factory method. One time when a factory method is useful is when object creation might fail. If data passed to the method is invalid, some resource (such as a database) prohibits the new object (perhaps a new Student has the same name as an existing Student), or the object may come from more than one place (for example, it may be either a new object or one taken from a pool of existing objects). In those cases, a factory method can return Nothing. A constructor could raise an error, but it cannot return Nothing if it fails.

If you want to force the program to use a factory method rather than creating an instance of the object directly, give the class a private constructor. Code that lies outside of the class cannot use the constructor because it is private. It also cannot use the default constructor associated with the New statement because the class has an explicit constructor. The code must create new objects by using the factory method, which can use the private constructor because it's inside the class.

As is the case with shared variables, you can access a shared method by using any instance of the class or by using the class's name. As is also the case with shared variables, if you access a shared method from an instance of the class, Visual Basic flags it with a warning.

The following code declares the student1 variable and initializes it by calling the NewStudent factory method using the class's name. Next, the code declares student2 and uses the student1 object's NewStudent method to initialize it.

Dim student1 As Student = Student.NewStudent()
Dim student2 As Student = student1.NewStudent()

One oddity of shared methods is that they can use class variables and methods only if they are also shared. If you think about accessing a shared method through the class name rather than an instance of the class, this makes sense. If you don't use an instance of the class, there is no instance to give the method data.

In the following code, the Student class declares the variable NumStudents with the Shared keyword so shared methods can use that value. It declares the instance variables FirstName and LastName without the Shared keyword, so shared methods cannot use those values. The shared NewStudent method starts by incrementing the shared NumStudents value. It then creates a new Student object and initializes its FirstName and LastName values. It can initialize those values because it is using a specific instance of the class and that instance has FirstName and LastName values.

Public Class Student
    Public Shared NumStudents As Integer
    Public FirstName As String
    Public LastName As String
    ...
    ' Return a new Student.
    Public Shared Function NewStudent() As Student
        ' Increment the number of Students loaded.
        NumStudents += 1

        ' Instantiate the Student.
        Dim new_student As New Student
        new_student.FirstName = "<unknown>"
        new_student.LastName = "<unknown>"

        ' Add the new student to the database.
        ...

        ' Return the new student.
Return new_student
    End Function
    ...
End Class

Figure 26-5 illustrates the situation. The shared NewStudent method is contained within the class itself and has access to the NumStudents variable. If it wanted to use a FirstName, LastName, or Scores value, however, it needs to use an instance of the class.

A shared method can only access other shared variables and methods.

Figure 26.5. A shared method can only access other shared variables and methods.

SUMMARY

Classes and structures are very similar. Both are container types that group related variables, methods, and events in a single entity.

Most developers use classes exclusively, primarily because structures are relatively new and developers are more familiar with classes. Structures also cannot take advantage of inheritance.

Another significant factor when picking between classes and structures, however, is their difference in type. Classes are reference types, whereas structures are value types. This gives them different behaviors when defining and initializing objects, and when passing objects to routines by value and by reference.

When you understand the differences between classes and structures, you can select the one that is more appropriate for your application.

If you build enough classes and structures, you may start to have naming collisions. It is common for developers working on different projects to define similar business classes such as Employee, Customer, Order, and InventoryItem. Although these objects may be similar, they may differ in important details. The Customer class defined for a billing application might include lots of account and billing address information, whereas a repair assignment application might focus on the customer's equipment and needs.

Having two Customer classes around can result in confusion and programs that cannot easily interact with each other. Namespaces can help categorize code and differentiate among classes. You can define separate namespaces for the billing and repair assignment applications, and use them to tell which version of the Customer class you need for a particular purpose.

Chapter 27, "Namespaces," describes namespaces in detail. It explains how to create namespaces and how to use them to refer to classes created in other modules.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.145.217