Previous section   Next section

5.1 Defining Classes

When you define a new class, you define the attributes of all objects of that class, as well as their behaviors. For example, if you are creating your own windowing operating system, you might want to create screen widgets (known as controls in Windows). One control of interest might be a listbox, which is very useful for presenting a list of choices to the user and enabling the user to select from the list.

Listboxes have a variety of characteristics: height, width, location, and text color, for example. Programmers have also come to expect certain behaviors of listboxes: they can be opened, closed, sorted, and so on.

Object-oriented programming allows you to create a new type, ListBox, which encapsulates these characteristics and capabilities.

To define a new type or class, you first declare it and then define its methods and fields. You declare a class using the Class keyword. The complete syntax is as follows:

[attributes] [access-modifiers] Class identifier 
[Inherits classname]
   {class-body}
End Class

Attributes are used to provide special metadata about a class (that is, information about the structure or use of the class) and are covered in Chapter 18.

Access modifiers are discussed later in this chapter. (Typically, your classes will use the keyword Public as an access modifier.)

The identifier is the name of the class that you provide. Typically, VB.NET classes are named with nouns (e.g., Dog, Employee, ListBox). The naming convention (not required, but strongly encouraged) is to use Pascal notation. In Pascal notation you use no underbars or hyphens, but if the name has two words (Golden Retriever) you push the two words together, each word beginning with an uppercase letter (GoldenRetriever).

The optional Inherits statement is discussed in Chapter 6.

The member definitions make up the class-body and are enclosed between the Class and End Class keywords.

Public Class Dog
   Dim age As Integer  'the dog's age
   Dim weight As Integer  'the dog's weight
   Public Sub Bark( ) 
     '....
   End Sub
End Class

All the things a Dog can do are described by methods within the class definition of Dog. The dog's attributes, or state, are described by the fields (member variables), such as age and weight.

5.1.1 Instantiating Objects

To make an actual instance, or object, of the Dog class, you must declare the object, and you must allocate memory for the object. These two steps combined are necessary to create, or instantiate, the object.

First, you declare the object by writing the access modifier (in this case, Dim), followed by an identifier (milo) for the object or instance of the class, the As keyword, and the type or class name (Dog):

Dim milo As Dog  'declare milo to be an instance of Dog

This is not unlike the way you create a local variable. Notice also that like with variables, the identifier for the object uses Camel Notation. Camel Notation is just like Pascal Notation except that the very first letter is lowercase. Thus, a variable or object name might be myDog, designatedDriver, or plantManager.

The declaration alone doesn't actually create an instance, however. To create an instance of a class you must also allocate memory for the object using the keyword New:

milo = New Dog( )  'allocate memory for milo

You can combine the declaration of the Dog type with the memory allocation into a single line:

Dim milo As New Dog( ) 

This declares milo to be an object of type Dog and also creates a new instance of Dog. You'll see what the parentheses are for later in this chapter in the discussion of the constructor.

In VB.NET, everything happens within a class. "But wait," I hear you cry, "we have been creating modules!" Yes, you've been writing code using modules, but when you compile your application a class is created for you from that module. This is VB.NET's strategy to continue to use modules (as VB6 did) but still comply with the .NET approach that everything is a class. (See Section 5.1.2, for further explanation.)

Given that everything happens within a class, no methods can run outside of a class, not even Main( ). The Main( ) method is the entry point for your program; it is called by the operating system, and it is where execution of your program begins. Typically, you'll create a small module to house Main( ):

Module modMain
   Public Sub Main( )
      ...
   End Sub
End Module

The compiler will turn this module into a class for you, as explained in the next section. However, it is somewhat more efficient for you to declare the class yourself:

Public Class Tester
  Public Sub Main( )
      Dim testObject As New Tester( )
   End Sub
   ' other members
End Class 

In the preceding code, you create the Tester class explicitly. Even though Tester was created to house the Main( ) method, you've not yet instantiated any objects of type Tester. To do so you would write:

Dim testObject As New Tester( ) 'make an instance of Tester

As you'll see later in this chapter, creating an instance of the Tester class allows you to call other methods on the object you've created (testObject).

One way to understand the difference between a class and an instance (object) of that class is to consider the distinction between the type Integer and a variable of type Integer.

You can't assign a value to a type:

Integer = 5  ' error

Instead, you assign a value to an object of that type, in this case, a variable of type Integer:

Dim myInteger As Integer
myInteger  = 5 'ok

Similarly, you can't assign values to fields in a class; you must assign values to fields in an object. Thus, you can't write:

Dog.weight = 5

This is not meaningful. It isn't true that every Dog's weight is 5 pounds. You must instead write:

milo.weight = 5

This says that a particular Dog's weight (milo's weight) is 5 pounds.

5.1.2 Modules Are Classes

You can see the relationship between modules and classes very easily. Begin by creating a new VB.NET console application called ModuleTest, as shown in Example 5-1.

Example 5-1. ModuleTest
Module Module1

    Sub Main( )
      Console.WriteLine("Hello from Module")
    End Sub

End Module

Using Visual Studio .NET, build this program and run it. Building the program saves an executable version on disk. Open ILDasm, which is the Intermediate Language Disassembler. ILDasm is a tool provided with the SDK that allows you to look at the Intermediate Language code produced by your program.

You might need to search for ILDasm on your disk. It is typically found in:

Program Files\Microsoft Visual Studio .NET 2003\FrameworkSDK\Bin

Open ILDasm and make the following menu choices: File->Open.

Navigate to your ModuleTest directory, and then navigate into the bin directory. Double-click on the .exe file. Expand the project, and you'll find a declaration of a class. Double-click on the class, and you'll see that Module1 has been declared to be a class, as shown in Figure 5-1.

Figure 5-1. Modules are classes
figs/pvn2_0501.gif

5.1.3 Memory Allocation: The Stack Versus the Heap

Objects created within methods are called local variables. They are local to the method, as opposed to belonging to the object, as member variables do. The object is created within the method, used within the method, and then destroyed when the method ends. Local objects are not part of the object's state; they are temporary value holders, useful only within the particular method.

Local variables of intrinsic types such as Integer are created on a portion of memory known as the stack. The stack is allocated and de-allocated as methods are invoked. When you start a method, all the local variables are created on the stack. When the method ends, local variables are destroyed.

These variables are referred to as local because they exist (and are visible) only during the lifetime of the method. They are said to have local scope. When the method ends, the variable goes out of scope and is destroyed.

VB.NET divides the world of types into value types and reference types. Value types are created on the stack. All the intrinsic types (Integer, Long, etc.) are value types, and thus are created on the stack.

Classes, on the other hand, are reference types. Reference types are created on an undifferentiated block of memory known as the heap. When you declare an instance of a reference type, what you actually are declaring is a reference. A reference is a variable that refers to another object. The reference acts like an alias for the object. That is, when you write:

Dim milo As New Dog( ) 

what actually happens is that the New operator creates a Dog object on the heap and returns a reference to it. That reference is assigned to milo. Thus, milo is a reference object that refers to a Dog object on the heap. It is common to say that milo is a reference to a Dog, or even that milo is a Dog object, but technically that is incorrect. milo is actually a reference object that refers to an (unnamed) Dog object on the heap.

The reference milo acts as an alias for that unnamed object. For all practical purposes, however, you can treat milo as if it were the Dog object itself.

The implication of using references is that you can have more than one reference to the same object. To see this difference between creating value types and reference types, examine Example 5-2. A complete analysis follows the output.

Example 5-2. Creating value types and reference types
Option Strict On
Imports System

Public Module Module1

   Public Class Dog
      Public weight As Integer
   End Class


   Public Class Tester

      Public Shared Sub Main( )
         Dim testObject As New Tester( )
         testObject.Run( )
      End Sub

      Public Sub Run( )
         ' create an integer
         Dim firstInt As Integer = 5

         ' create a second integer
         Dim secondInt As Integer = firstInt

         ' display the two integers 
         Console.WriteLine( _
            "firstInt: {0} secondInt: {1}", firstInt, secondInt)

         ' modify the second integer
         secondInt = 7

         ' display the two integers
         Console.WriteLine( _
            "firstInt: {0} secondInt: {1}", firstInt, secondInt)

         ' create a dog
         Dim milo As New Dog( )

         ' assign a value to weight
         milo.weight = 5

         ' create a second reference to the dog
         Dim fido As Dog = milo

         ' display their values
         Console.WriteLine( _
            "Milo: {0}, fido: {1}", milo.weight, fido.weight)

         ' assign a new weight to the second reference
         fido.weight = 7

         ' display the two values
         Console.WriteLine( _
           "Milo: {0}, fido: {1}", milo.weight, fido.weight)
      End Sub

   End Class

End Module
Output:
firstInt: 5 secondInt: 5
firstInt: 5 secondInt: 7
Milo: 5, fido: 5
Milo: 7, fido: 7

In Example 5-2, you create a class named Tester within your module. (Remember that the module itself will be converted to a class at compile time; that class will contain the Tester class.) You must mark Main( ) with the keyword Shared. (The Shared keyword is covered later in this chapter.)

Within Main( ), you create an instance of the Tester class and you call the Run( ) method on that instance:

Public Shared Sub Main( )
   Dim testObject As New Tester( )
   testObject.Run( )
End Sub

Run( ) begins by creating an integer, firstInt, and initializing it with the value 5. The second integer, secondInt, is then created and initialized with the value in firstInt. Their values are displayed as output:

firstInt: 5 secondInt: 5

Because Integer is a value type, a copy of the value is made, and secondInt is an independent second variable, as illustrated in Figure 5-2.

Figure 5-2. secondInt is a copy of firstInt
figs/pvn2_0502.gif

Then the program assigns a new value to secondInt:

secondInt = 7

Because these variables are value types, independent of one another, the first variable is unaffected. Only the copy is changed, as illustrated in Figure 5-3.

Figure 5-3. Only the copy is changed
figs/pvn2_0503.gif

When the values are displayed, they are now different:

firstInt: 5 secondInt: 7

Your next step is to create a simple Dog class with only one member: a public variable weight.

Generally you will not make member variables public. The weight field was made public to simplify this example. The use of the Public keyword and other access modifiers is explained later in this chapter.

You instantiate a Dog object and save a reference to that Dog object in the reference milo:

Dim milo As New Dog( )

You assign the value 5 to milo's weight field:

milo.weight = 5

You commonly say that you've set milo's weight to 5, but actually you've set the weight of the unnamed object on the heap to which milo refers, as shown in Figure 5-4.

Figure 5-4. milo is a reference to an unnamed Dog object
figs/pvn2_0504.gif

Next you create a second reference to Dog and initialize it by setting it equal to milo. This creates a new reference to the same object on the heap.

Dim fido As Dog = milo

Notice that this is syntactically similar to creating a second Integer variable and initializing it with an existing Integer, as you did before:

Dim secondInt As Integer = firstInt
Dim fido As Dog = milo

The difference is that Dog is a reference type, so fido is not a copy of milo; it is a second reference to the same object to which milo refers. That is, you now have an object on the heap with two references to it, as illustrated in Figure 5-5.

Figure 5-5. fido is a second reference to the Dog object
figs/pvn2_0505.gif

When you change the weight of that object through the fido reference:

fido.weight = 7

you are changing the weight of the same object to which milo refers. This is reflected in the output:

Milo: 7, fido: 7

It isn't that fido is changing milo, it is that by changing the (unnamed) object on the heap to which fido refers, you are simultaneously changing the value of milo because they refer to the same unnamed object.

5.1.4 Creating a Time Class

Now consider a class to keep track of and display the time of day. The internal state of the class must be able to represent the current year, month, date, hour, minute, and second. You probably would also like the class to display the time in a variety of formats.

You might implement such a class by defining a single method and six variables, as shown in Example 5-3.

Example 5-3. The Time class
Option Strict On
Imports System

Public Class Time
   ' Private variables
   Private year As Integer
   Private month As Integer
   Private date As Integer
   Private hour As Integer
   Private minute As Integer
   Private second As Integer
   ' Public methods
   Public Sub DisplayCurrentTime( )
      Console.WriteLine("Stub for DisplayCurrentTime")
   End Sub 'DisplayCurrentTime
End Class 'Time

Module Module1

   Sub Main( )
      Dim timeObject As New Time( )
      timeObject.DisplayCurrentTime( )
   End Sub

End Module

This code creates a new user-defined type: Time. The Time class definition begins with the declaration of a number of member variables: Year, Month, Date, Hour, Minute, and Second. The keyword Private indicates that these values can only be called by methods of this class. The Private keyword is an access modifier, the use of which is explained later in this chapter.

Many VB.NET programmers prefer to put all of the member fields together, either at the very top or the very bottom of the class declaration, though that is not required by the language.

The only method declared within the Time class is the method DisplayCurrentTime( ). The DisplayCurrentTime( ) method is defined as a sub procedure or subroutine; as explained in Chapter 2, that means it will not return a value to the method that invokes it. For now, the body of this method has been "stubbed out."

Stubbing out a method is a temporary measure you might use when you first write a program to allow you to think about the overall structure without filling in every detail when you create a class. When you stub out a method body, you leave out the internal logic and just mark the method, as done here, perhaps with a message to the console:

Public Sub DisplayCurrentTime( )
   Console.WriteLine("Stub for DisplayCurrentTime")
End Sub 'DisplayCurrentTime

When you create the project, VS.NET creates the module, named Module1. Within the module, you define your Main( ) method, and within Main( ) you can instantiate a Time object:

Module Module1
   Sub Main( )
      Dim timeObject As New Time( )

Because timeObject is an instance of Time, Main( ) can make use of the DisplayCurrentTime( ) method available with objects of that type and call it to display the time:

timeObject.DisplayCurrentTime( )

You invoke a method on an object by writing the name of the object (timeObject) followed by the dot operator (.), followed by the method name and parameter list in parentheses (in this case, empty). You'll see how to pass in values to initialize the member variables in the discussion of constructors, later in this chapter.

5.1.5 Access Modifiers

An access modifier determines which class methods�including methods of other classes�can see and use a member variable or method within a class. Table 5-1 summarizes the VB.NET access modifiers.

Table 5-1. Access modifiers

Access modifier

Restrictions

Public

No restrictions. Members that are marked Public are visible to any method of any class.

Private

The members in class A that are marked Private are accessible only to methods of class A.

Protected

The members in class A that are marked Protected are accessible to methods of class A and also to methods of classes derived from class A. The Protected access modifier is used with derived classes, as explained in Chapter 6.

Friend

The members in class A that are marked Friend are accessible to methods of any class in A's assembly.[2]

Protected Friend

The members in class A that are marked Protected Friend are accessible to methods of class A, to methods of classes derived from class A, and also to any class in A's assembly. This is effectively Protected or Friend (There is no concept of Protected and Friend.)

[2] An assembly is a collection of files that appear to the programmer as a single executable (exe) or DLL.

The Time class and its DisplayCurrentTime( ) method are both declared public so that any other class can make use of them. If DisplayCurrentTime( ) had been private, it would not be possible to invoke DisplayCurrentTime from any method of any class other than methods of Time. In Example 5-3, DisplayCurrentTime( ) was invoked from a method of Tester (not Time), and this was legal because both the class (Time) and the method (DisplayCurrentTime) were marked public.

.

It is good programming practice to explicitly set the accessibility of all methods and members of your class.


  Previous section   Next section
Top