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. On the other hand, 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 a 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")
Structure and class constructors are very similar, but there are some major differences. A structure cannot declare a constructor that takes no parameters. It also 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 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 value NOT allowed. Public LastName As String ' = "<unknown>" ' Initialization value 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
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 that 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.
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.
The following code demonstrates the Finalize method. 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 Go button, the btnGo_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 btnGo_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.
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 btnGo_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnGo.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
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.
The following example code tests the Finalize method when the program ends. 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.
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("Number: " & m_Number) End Sub End Class ' Make objects until garbage collection starts. Private Sub btnGo_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnGo.Click Static i As Integer = 0 i += 1 Dim new_numbered As New Numbered(i) Me.Text = i.ToString End Sub End Class
If your class allocates unmanaged resources, you should give it a Finalize method to free them.
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 them 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 the program calls Dispose or not, 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.
The following code demonstrates the Dispose and Finalize methods. 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 only 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
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(ByVal sender As System.Object, _ ByVal e As System.EventArgs) 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(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnNoDispose.Click Static i As Integer = 0 i += 1 Dim obj As New Named("No Dispose " & i) End Sub End Class
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.
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 4. For information on declaring methods, see Chapter 6, 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. Usually, you can provide access routines that make using the object’s data easier to understand.
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.