BUY THIS BOOK
Add to Cart

Print Book $34.95


Safari Books Online

What is this?

Add to UK Cart

Print Book £24.95

What is this?

Looking to Reprint this content?

Object-Oriented Programming with Visual Basic .NET
Object-Oriented Programming with Visual Basic .NET

By J.P. Hamilton
Price: $34.95 USD
£24.95 GBP

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Introduction
To understand the world of object-oriented programming, look at the world around you for a moment. You might see vacuum cleaners, coffee makers, ceiling fans, and a host of other objects. Everywhere you look, objects surround you.
Some of these objects, such as cameras, operate independently. Some, such as telephones and answering machines, interact with one another. Some objects contain data that persists between uses, like the address book in a cell phone. Some objects contain other objects, like an icemaker inside of the freezer.
Many objects are similar in function but different in purpose. Bathtubs and kitchen sinks, for example, both provide water and are used for cleaning. But it is a rare occasion when you will take a bath in the kitchen sink or wash your dishes in the tub. However, the bathtub and the kitchen sink in your house probably share the same plumbing. Certainly, they share a common interface: hot and cold water knobs, a faucet, and a drain.
When you think about it, what is the difference between a sink and a bathtub? The location? The size of the basin? Their heights off the ground? How many more similarities are there than differences?
Sometimes the same action causes an object to do different things depending on the context of the situation. When you press Play on the remote, the DVD might play a movie on the television. But if a CD is in the player, it plays music out of the speakers. Same button, same action—different results. When you flip the switch on the back porch, the light comes on. But the switch in the kitchen turns on the garbage disposal. You use the same kind of switch, but obtain different results.
You can think about many objects around you in terms of black boxes. You comprehend the fundamentals of these objects and possess a basic understanding of what makes them work, but the specifics of their operation are unknown to you. And you like it that way. Do you really want to have to know the inner mechanisms of every object in your house in order to use it?
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Visual Basic .NET and Object-Oriented Programming
Visual Basic .NET is a fully object-oriented programming language, which means it supports the four basic tenets of object-oriented programming: abstraction, encapsulation, inheritance, and polymorphism.
We have already conceptualized many of these object-oriented concepts by just looking at the objects that surround us in our everyday lives. Let's look more closely at these terms and see what they actually mean and what they do for developers of object-oriented software.
A radio has a tuner, an antenna, a volume control, and an on/off switch. To use it, you don't need to know that the antenna captures radio frequency signals, converts them to electrical signals, and then boosts their strength via a high-frequency amplification circuit. Nor do you need to know how the resulting current is filtered, boosted, and finally converted into sound. You merely turn on the radio, tune in the desired station, and listen. The intrinsic details are invisible. This feature is great because now everyone can use a radio, not just people with technical know-how. Hiring a consultant to come to your home every time you wanted to listen to the radio would become awfully expensive. In other words, you can say that the radio is an object that was designed to hide its complexity.
If you write a piece of software to track payroll information, you would probably want to create an Employee object. People come in all shapes, sizes, and colors. They have different backgrounds, enjoy different hobbies, and have a multitude of beliefs. But perhaps, in terms of the payroll application, an employee is just a name, a rank, and a serial number, while the other qualities are not relevant to the application. Determining what something is, in terms of software, is abstraction.
In object-oriented software, complexity is managed by using abstraction. Abstraction is a process that involves identifying the crucial behavior of an object and eliminating irrelevant and tedious details. A well thought-out abstraction is usually simple, slanted toward the perspective of the user (the developer using your objects), and has probably gone through several iterations. Rarely is the initial attempt at an abstraction the best choice.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The .NET Framework
The objects you construct with VB.NET will live out their lives within the .NET Framework, which is a platform used to develop applications. The platform was designed from the ground up by using open standards and protocols like XML, HTTP, and SOAP. It contains a rich standard library that provides services available to any language running under its protection.
The impetus behind its creation was the desire to develop a platform for building, deploying, and running web-based services. In spite of this goal, the framework is ideal for developing all types of applications, regardless of the design. The .NET Framework makes child's play of some of programming's most sophisticated concepts, giving you the ability to take advantage of today's cutting-edge architectures:
  • Distributed computing using open Internet standards and protocols such as HTTP, XML, and SOAP
  • Enterprise services such as object pooling, messaging, security, and transactions
  • An infrastructure that simplifies the development of reusable cross-language compatible components that can be deployed over the Internet
  • Simplified web development using open standards
  • Full language integration that make it possible to inherit from classes, catch exceptions, and debug across different languages
Deployment is made simpler because settings are stored in XML-based configuration files that reside in the application directory; there is no need to go to the registry. Shared DLLs must have a unique hash value, locale, and version, so physical filenames are no longer important once these considerations are met. Not having physical filenames makes it possible to have several different versions of the same DLL in use at the same time, which is known as side-by-side execution. All dependencies and references are stored within the executable in a section called the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: Object Fundamentals
Before designing and building objects for the .NET environment, understanding the environment itself is important. In this respect, a little bit of code goes a long way. This chapter deviates from the standard "Hello, world" application in favor of a "Hello, world" component. Then it builds a small client that uses the component to display a message to the console window.
Example 2-1 contains the listing for our "Hello, world" component. It contains a single class named Hello with a single method named Write. Save the listing to a file named hello.vb. The rest of the chapter will use this listing as a foundation of discussion.
All Visual Basic source code should be saved to files with a .vb extension. One file can contain one class or several classes. How you organize the code is up to you.
Example 2-1. The "Hello, world" component
Option Strict On
   
Imports System
   
Namespace Greeting
   
Public Class Hello
    Public Sub Write(ByVal value As String)
        Console.WriteLine("Hello, {0}!", value)
    End Sub
End Class
   
End Namespace
The Visual Basic .NET command-line compiler is a program called vbc.exe that should be in your path once the .NET Framework is installed. All examples in this book assume that the example code exists in the root directory of your hard drive. This assumption is made to improve readability. If the code is not in your hard drive's root directory, you need to specify a fully qualified pathname to the compiled file or compile from the directory where the source code is located. With this in mind, you should be able to compile Example 2-1 to a dynamic link library (DLL) as follows:
C:\>vbc /t:library hello.vb
The /t: option is short for target, which can be one of the following values:
exe
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Creating and Compiling the Component
Example 2-1 contains the listing for our "Hello, world" component. It contains a single class named Hello with a single method named Write. Save the listing to a file named hello.vb. The rest of the chapter will use this listing as a foundation of discussion.
All Visual Basic source code should be saved to files with a .vb extension. One file can contain one class or several classes. How you organize the code is up to you.
Example 2-1. The "Hello, world" component
Option Strict On
   
Imports System
   
Namespace Greeting
   
Public Class Hello
    Public Sub Write(ByVal value As String)
        Console.WriteLine("Hello, {0}!", value)
    End Sub
End Class
   
End Namespace
The Visual Basic .NET command-line compiler is a program called vbc.exe that should be in your path once the .NET Framework is installed. All examples in this book assume that the example code exists in the root directory of your hard drive. This assumption is made to improve readability. If the code is not in your hard drive's root directory, you need to specify a fully qualified pathname to the compiled file or compile from the directory where the source code is located. With this in mind, you should be able to compile Example 2-1 to a dynamic link library (DLL) as follows:
C:\>vbc /t:library hello.vb
The /t: option is short for target, which can be one of the following values:
exe
A console application. If the /t switch is omitted, this is the default value.
winexe
A Windows executable.
library
A DLL.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Namespaces
All classes are members of some namespace. You can think of a namespace as a user-defined scope—an organizational construct that allows you to group your classes in a meaningful way and uniquely identify your classes and their members in case of naming conflicts. The Hello class from Example 2-1 is a member of a namespace called Greeting denoted by the Namespace block surrounding its definition. Even if the namespace block were removed, the Hello class would still be considered a member of the root namespace, which is scoped to the executable.
Every time anything is compiled with VB, two class libraries—mscorlib.dll and Microsoft.VisualBasic.dll—are referenced implicitly. The latter contains classes that provide backward compatibility to earlier versions of Visual Basic, while the former contains portions of the System and several other namespaces. Notice the second line of code in Example 2-1:
Imports System
This line brings the System namespace into the scope of the current file, hello.vb. This is done for the benefit of the call to Console.WriteLine, which writes a message to the console window. Without the Imports directive, the Console class could be referred to only through its namespace, which means the call would look like this:
Public Class Hello
    Public Sub Write(ByVal value As String)
        System.Console.WriteLine("Hello, {0}!", value)
    End Sub
End Class
This particular situation is not too bad, but if the file contained several calls to Console.WriteLine, things could get ugly. As most of the .NET class library is contained within the System namespace, importing it is usually your best option.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Using a Component
Now that you have a component, you need a way to use it. Example 2-2 contains a listing for a simple client. Save it to a file named hello-client.vb. Let's see how everything fits together, and then you can compile it.
Example 2-2. "Hello, world" client
Imports System
Imports Greeting
   
Public Class Application
   
  Public Shared Sub Main( )
    Dim hw As New Hello( )
    hw.Write("World")
    Console.ReadLine( )
  End Sub
   
End Class
The Greeting namespace defined in Example 2-1 was imported. Without it, every class in the Greeting namespace would have to be referenced directly, as in the following code:
Public Shared Sub Main( )
  Dim hw As New Greeting.Hello( )
  hw.Write("World")
End Sub
All standalone executables require an entry point with this signature:
Public Shared Sub Main( )
This is where everything begins, but as you can see, not much is going on. The only thing the client does is declare an instance of the Hello class (which is defined in the component) and call its Write method.
When compiled, Example 2-2 must explicitly reference hello.dll for everything to compile. This can be accomplished with the /r compiler option. Assuming that the DLL lives in the same directory as the client code, this executable can be compiled as follows:
C:\>vbc /t:exe /r:hello.dll hello-client.vb
This compilation produces an executable named hello-client.exe. You can change the name of the output file by using the /out compiler option:
C:\>vbc /t:exe /r:hello.dll /out:hello.exe hello-client.vb
When the executable runs, the following code is dumped to the console:
Hello, World!
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Application Domains
In an unmanaged Windows environment, applications are isolated from one another by process boundaries. As shown in Figure 2-1, each Win32 program is given its own process and a 4 GB virtual address space to go along with it. Additional libraries or components share this address space with their client. The operating system handles all the work associated with mapping the virtual address space to an actual address in memory. The advantage of this isolation is that if a program crashes, it won't take the entire system down—just the current process.
Figure 2-1: The Win32 process boundary
Under .NET, the CLR manages the memory; Windows doesn't give it to you directly. One reason for this is garbage collection. The CLR needs to know where memory is located to free it or determine if it is in use at all, which is why no pointers are allowed in managed code. This level of indirection, in regard to memory, allows the CLR to provide application isolation with more granularity. In .NET, an entity called an application domain determines isolation.
As shown in Figure 2-2, several application domains can exist within the same process and still remain isolated from one another. They can also be loaded and unloaded independently of the process, which means that if a crash occurs, the offending application domain can be unloaded without affecting the entire process. This is highly efficient, considering the amount of overhead involved in creating a process.
Figure 2-2: Application domains provide isolation for .NET applications within a process.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Contexts
Application domains are subdivided further into contexts. Think of a context as a group of objects that share the same rules of use. These rules include such things as just-in-time activation, security, synchronization, thread affinity, transactions, and security. Under ordinary circumstances, application domains contain only one context: the default context. However, in some situations, the application domain contains additional contexts. Objects that support transactions (provided by COM+), for instance, would be contained in a separate context. These objects are known as context-bound objects.
Now that you better understand the execution environment of a .NET executable, let's look at the executable itself.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Assemblies
When you have an instance of a class, you are said to have an object. A collection of object definitions comprises a component. For instance, msado15.dll is a COM component that contains the Connection, Command, and Recordset objects (among others) found in ADO. In .NET, the concept of an assembly is roughly analogous to that of a component, but you can think of it as more of a "super component."
In addition to your program's code, assemblies contain a manifest, which is a block of metadata that describes everything in the assembly and how it relates to everything else. As shown in Figure 2-3, it contains references to other assemblies that the current assembly might need, as well as a description of the types contained within the assembly. These references make the assembly self-describing, alleviating the need for type libraries and IDL files. In Visual Basic, assemblies can be a single executable with a Sub Main entry point or a class library in a DLL.
Figure 2-3: Structure of an assembly
Assemblies can also contain nonexecutable files of any type, similar to a resource file in a traditional Windows executable. The difference is that these additional files do not have to exist as binary information that is part of the executable. Every file that comprises an assembly can retain its individual identity within the filesystem. However, as far as the runtime is concerned, they are a single, cohesive unit. Multimodule assemblies, which contain resources in addition to code, are built using the Assembly Linker utility (al.exe) that is part of the .NET Framework SDK. The VB compiler only emits single module assemblies containing code.
Assemblies are the fundamental units in which code is deployed, version information is specified, and security permissions are defined. An assembly also represents a boundary for the identity of a type. If two different assemblies contain the same type definition, the runtime considers each a different type. This happens irrespective of the namespace the two types are defined within. Remember that namespaces are just mechanisms for organizing type information; the CLR resolves type names through namespaces.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Intermediate Language
VB is not compiled directly into machine code. It is first compiled to a CPU-independent language called Microsoft Intermediate Language (MSIL, or simply IL). You might think this compilation is a throwback to VB's early years as an interpreted language, but the situation is not so grim. The code is not interpreted; eventually, it is converted to machine code at runtime by a just-in-time (JIT) compiler. This happens during execution, as code is needed. Then it is cached as machine code until the process terminates.
The .NET Framework SDK ships with an IL disassembler called ILDASM, which allows you to view the IL produced by the VB compiler (or any .NET compiler, for that matter). This feature can be very useful if you want to see how something in the .NET class library was implemented or to determine what classes are available in a particular library. The hello.dll assembly can be examined by running the IL Disassembler (ildasm.exe) from the command line:
C:\>ildasm hello.dll
From the ILDASM dialog, you can view the manifest and navigate every namespace within the given assembly. As shown in Figure 2-4, ILDASM presents a tree view that allows inspection of the manifest, the various namespaces, classes, and methods contained within the assembly. Example 2-3 contains the entire IL listing for hello.dll, which was produced by selecting File/Dump from the menu.
Figure 2-4: The ILDASM dialog
Example 2-3. The IL dump of hello.dll
//  Microsoft (R) .NET Framework IL Disassembler.  Version 1.0.3705.0
//  Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.
   
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
  .ver 1:0:3300:0
}
.assembly extern Microsoft.VisualBasic
{
  .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
  .ver 7:0:3300:0
}
.assembly hello
{
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.module hello.dll
// MVID: {8A2071A6-F906-43C1-B6DB-CA5058F54BC4}
.imagebase 0x00400000
.subsystem 0x00000002
.file alignment 512
.corflags 0x00000001
// Image base: 0x03090000
//
// ============== CLASS STRUCTURE DECLARATION ==================
//
.namespace Greeting
{
  .class public auto ansi Hello
         extends [mscorlib]System.Object
  {
  } // end of class Hello
   
} // end of namespace Greeting
   
// =============================================================
   
// =============== GLOBAL FIELDS AND METHODS ===================
   
// =============================================================
   
// =============== CLASS MEMBERS DECLARATION ===================
//   note that class flags, 'extends' and 'implements' clauses
//          are provided here for information only
   
.namespace Greeting
{
  .class public auto ansi Hello
         extends [mscorlib]System.Object
  {
    .method public specialname rtspecialname 
            instance void  .ctor( ) cil managed
    {
      // Code size       7 (0x7)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  call       instance void [mscorlib]System.Object::.ctor( )
      IL_0006:  ret
    } // end of method Hello::.ctor
   
    .method public instance void  Write(string 'value') cil managed
    {
      // Code size       12 (0xc)
      .maxstack  8
      IL_0000:  ldstr      "Hello, {0}!"
      IL_0005:  ldarg.1
      IL_0006:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                    object)
      IL_000b:  ret
    } // end of method Hello::Write
   
  } // end of class Hello
   
// =============================================================
   
} // end of namespace Greeting
   
//*********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file C:\hello.res
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The Global Assembly Cache
If you use an object from the .NET class library in your own code, you have to reference the assembly in which it is defined at compile time. For instance, if you want to use the MessageBox class in one of your applications, you need to make sure that the System.Windows.Forms assembly is made available to the compiler. This assembly contains the System.Windows.Forms namespace, which in turn contains the MessageBox class. The command-line statement needed to compile your source code is:
vbc /t:winexe /r:System.Windows.Forms.dll mycode.vb
Referencing one of your own assemblies is no different, but a full path to the assembly is expected. For example:
vbc /t:library /r:<path>\mylib.dll mycode.vb
A path is not necessary for System.Windows.Forms.dll because like all .NET class library assemblies, it lives in the global assembly cache (GAC). The GAC is a directory, shown in Figure 2-5, that contains assemblies that are meant to be shared by several applications on a single machine. The actual path to the GAC is <%windir%>/assembly.
Figure 2-5: The GAC in an Explorer list pane
If you want to share an assembly by putting it in the GAC, it must have a strong name. A strong name defines the assembly's identity: its name, version number, and culture information (if it exists), a public key, and a digital signature.

Section 2.8.1.1: Strong Name utility

The first step toward giving an assembly a strong name is creating a key pair file that contains the public/private keys used to sign the assembly. To create the key pair file, use the Strong Name tool from the command line:
sn -k hello.snk
Doing so creates a key pair file named hello.snk.

Section 2.8.1.2: Assembly Linker

Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
System Namespace
Every time you compile anything in Visual Basic, the compiler automatically references two assemblies: mscorlib.dll and Microsoft.VisualBasic.dll. These two components contain the System and Microsoft.VisualBasic namespaces, respectively (a small portion of the System namespace is also contained in System.dll).
The System namespace is the root namespace of primary types in .NET and contains the base data types used by all languages in the framework. When you declare a primitive data type in VB, it is actually mapped to a type defined in this namespace. Table 2-1 provides a list of the types in System and how they relate to Visual Basic.
Table 2-1: Data type mappings from System to VB
System
Visual Basic
Description
Byte
Byte
8-bit unsigned integer
Int16
Short
16-bit signed integer
Int32
Integer
32-bit signed integer
Int64
Long
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Class Anatomy
Encapsulation is the idea that data and the functions that manipulate that data are kept together. In Visual Basic .NET, encapsulation is achieved with the class, which can be thought of as the blueprint of an object. Classes contain member variables that are used to hold the state of an object—brightness, contrast, hue—and member functions (also known as methods) that describe the behavior or operations that can be performed on the object: turn on, turn off, or change the channel. They also are a source of events, which notify clients on the current affairs of the object.
Figure 3-1 illustrates the basic structure of a class, which contains four primary entities: member variables, methods (or member functions), properties, and events. As Example 3-1 shows, the class can exist as part of a namespace (or not).
Figure 3-1: Class overview
Example 3-1. Structure of a class block
Namespace [Namespace Name]
   
Public Class [Class Name]
   
    'Member variables
   
    'Methods
   
    'Properties
   
    'Events
    
End Class
   
End Namespace
Each class entity (with the exception of events) comes in two distinct flavors, instance or shared. Instance variables, methods, and properties operate on a specific instance of a class. When an object is created (instantiated), it receives its own copy of all the member data defined by the class. All method (and property) calls are made through that specific instance.
Example 3-2 contains a shared method named Hello. Shared entities are mutual across all class instances, so you don't need an actual instance of the class to call the method:
Console.WriteLine(World.Hello( ))
In addition to being associated with a data type, all class entities (events included), and the class itself, are associated with an access modifier. These modifiers determine whether a client can access the entity or if the entity is available only internally within the object, in its descendents, or in the executable in which it is defined.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Member Variables
As shown in Example 3-1, member variables of a class are typically declared at the top of the class block (with or without an initial state).
The term "member variables" also includes constants and enumerations. By default, constants and enumerations are shared, so an instance of the class is not necessary; these values pertain to every instance of an object. Constants can refer to any data type, while enumerations are restricted to Byte, Short, Integer, and Long. Example 3-2 illustrates the definition of member variables in a class.
Example 3-2. Member variables
Public Class World
   
    'Member data represents state
    Private age As Double
   
    'Constants
    Public Const AverageDensity As Single = 5515  '(kg/m3)
    Public Const PolarRadius = 6356.8             'In km
    Public Const SatelliteCount As Byte = 1       'The Moon
   
    'Enums
    Public Enum Continents
        Africa = 1
        Antarctica = 2
        Asia = 3
        Australia = 4
        Europe = 5
        NorthAmerica = 6
        SouthAmerica = 7
    End Enum
   
    Public Shared Sub Hello( )
        Console.WriteLine("Hello, World!")
    End Sub
   
End Class
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Properties
In traditional OOP languages like C++, it is considered good practice to provide accessor methods to manipulate an object's state. For instance, if an object has a member variable named weight, a programmer often writes two functions. One function is used to get the value of weight, and another to set the value—something akin to get_Weight and set_Weight. To provide read-only access, the programmer would forego the get_ method.
Properties in VB.NET are merely a language construct used to provide this frequently used functionality and enforce good programming practice through the language.
Whenever you create a property, VB.NET automatically generates a get_ and set_ method behind the scenes. Essentially, a property is nothing more than language convenience. Check it out with ILDASM.
Properties can be ReadOnly, WriteOnly, or both. Read-only properties contain a Get block that allows retrieval of a value, but prevents it from being changed:
Public ReadOnly Property Age( ) As Double
    Get
        Return Me.age
    End Get
End Property
Write-only properties use a Set block, which allows values to be initialized but not retrieved:
Public WriteOnly Property Age( ) As Double
    Set 
        'age is a Private member variable           
        Me.age = value
    End Set
End Property
Properties that provide both read and write access must specify only an access modifier:
Public Property Age( ) As Double
    Get
        Return Me.age
    End Get
    Set (ByVal value As Double)
        Me.age = value
    End Set
End Property
In the Set block, you can specify the name and type of the incoming value. If you choose not to, the compiler uses "value" as the placeholder for the property value.
The ReadOnly keyword can also be applied to member data. These fields can be initialized during object construction, but not after. Usually, member data, like the private member
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Methods
In VB.NET, methods come in three varieties: functions, subroutines, and properties.
Functions allow you to return a value resulting from an operation. They are defined with the Function keyword and can contain any number of parameters. They can also return a result to the caller with the Return keyword:
Public Class SpaceTime
   
    'In meters per second
    Private Const LightSpeed As Double = 299792458
    
    Private Function Energy(mass As Double) As Double
       'Same as LightSpeed * LightSpeed * mass
        Return LightSpeed *= LightSpeed * mass   
            End Function
   
    'Really technical stuff goes here
End Class
Subroutines perform tasks that do not require a return value and are declared just like functions, except they are defined using the Sub keyword:
Public Class SpaceTime
   
    Public Sub FoldSpace( )
        'Suprisingly simple
    End Sub
    
End Class
A class should represent one and only one abstraction. The methods of a class should use most of the member data within the class most of the time. This means that if you find that half of the class methods operate on half of the data members, but the other methods use the remaining half, there is an implied division within the class. You probably have two classes instead of one.
To formulate this concept as a general principle, make sure there are no broken lines of communication within a class.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Access Modifiers
Everything that comprises a class is associated with an access modifier that determines the scope or visibility of the entity in question. This is where the art of object design lies. Beyond the semantics of declaring member data and methods, an understanding of these access modifiers is crucial. Each modifier has a few basic guidelines geared toward designing robust, object-oriented code. Figure 3-2 shows the relationships between the four basic modifiers: Public, Private, Protected, and Friend.
Figure 3-2: Access modifier relationships
Public
Public classes are visible to everyone and not restricted in any way. Public members are visible inside and outside the class in which they were declared.
Member variables are not typically defined with public scope because this definition would allow unrestricted access to an object's state. In solid OO designs, member variables are kept private.
Private
Private classes can only be nested within another class and are completely restricted outside of that scope; it is not possible to declare a standalone class with this modifier. Private classes encapsulate functionality that is specific to the class where they were declared.
Private member variables are accessible only within the current class where they were declared. Even derived classes do not have access to Private members; they can be accessed through properties.
Private is the most restrictive modifier.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Passing Parameters
Data types fall into two categories: values types and reference types. Examples of value types include the built-in data types like Short, Integer, and Long (System.Int16, System.Int32, and System.Int64). Value types are created on the stack, which is the area of memory that is local to a method. When the location is destroyed, the value type is destroyed—the garbage collector does not manage the stack.
Reference types are defined using classes and created on the heap, which is available for the lifetime of the object. The garbage collector manages this area of memory.
By default, both types are passed by value; a copy of the value is placed on the stack. Therefore, any changes made to a variable are local to the method. Example 3-4 demonstrates this concept.
Example 3-4. Passing parameters ByVal
Imports System
   
Public Class App
   
  Private Class Counter
   
    Public Sub Increment(ByVal x As Integer)
      x += 1
    End Sub
   
  End Class
   
  Public Shared Sub Main( )
    Dim cc As New Counter( )
    Dim x As Integer = 1
    cc.Increment(x)
    Console.WriteLine(x.ToString( ))
    Console.WriteLine( )
    Console.WriteLine("Hit ENTER to continue.")
    Console.ReadLine( )
  End Sub
   
End Class
When Example 3-4 is executed, the output will be 1 because a copy of x was passed to the Counter.Increment method. It is useful to use the ByVal keyword explicitly instead of relying on the default behavior:
Public Sub Increment(ByVal x As Integer)
Replacing ByVal with ByRef causes a reference to be passed instead of a copy. A reference is similar to a pointer. However, a reference should be thought of as an alias of a data type, not some arbitrary location in memory.
Public Sub Increment(ByRef x As Integer)
The output resulting from this change will be 2.
Here is where things might get a little confusing. The previous example used a value type (an
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Value Types
Reference types and value types are both derived from System.Object. However, value types are treated differently in terms of allocation. When a value type is passed to a ByVal method, the actual value is copied. Also, assigning a value type to another instance of that type causes the value to be copied. The exception occurs when a value type is a reference type's data member; then it is heap allocated right along with the rest of the class.
In the following code fragment, number1 and number2 are two distinct locations in memory that each contain the value 5:
Dim number1 As Integer = 5
Dim number2 As Integer = number1
This contrasts with reference types (classes). Consider the following fragment, in which two objects are instantiated and then one is assigned to the other:
Dim object1 As New Object( )
Dim object2 As New Object( )
   
object2 = object1
After the assignment, there are no longer two objects; object2 is now a reference to object1. The location in memory formerly associated with object2 is now sitting around waiting to be garbage collected.
Sometimes defining a value type is beneficial—especially when you have small amounts of related data (that consist of value types). A perfect example is the Point structure:
Public Structure Point
    Public x As Integer
    Public y As Integer
End Structure
Value types always contain known values. The compiler automatically generates a default constructor that is used to initialize all the values of the structure to a known value.
Once a Point is declared, it can be used just like any other value type. There is no need to use the New operator because the default constructor is called automatically:
'After assignment p1 and p2 are two different points
   
Dim p1 As Point
Dim p2 As Point
   
p1.x = 10
p1.y = 10
   
p2 = p1
Structures have many of the same features as classes—like constructors, for instance. While the use of a default constructor is restricted (no arguments), it is legal to define a constructor that contains parameters:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Creation and Destruction
The lifetime of an object running under .NET is not as straightforward as it is in other languages like C++ or managed VB.
In these languages, when an object is created, its constructor is called, which allows the object to be initialized. When the object goes out of scope, its destructor is called, providing a convenient place to free resources; this is usually the point at which database connections are closed, file handles are freed, and memory allocated during the object's lifetime is released. The object has a practical, convenient mechanism for cleanup. But as you will see, .NET handles things differently.
VB.NET does provide the means to declare a constructor similar to that of Java, C++, and most other OO languages. In this respect, VB.NET is similar to these languages. However, in VB.NET, the declaration of a constructor is a little more intuitive. The constructor for an object is a method named New, which makes perfect sense because this is what is called when you declare a new instance of an object. The following fragment calls the default constructor for the Hello class:
 Dim hello As New Hello( )    'Calls Hello.New( )
The default constructor does not have any arguments, and, like all constructors, it does not return a value:
Public Class Hello
    Public Sub New( )
        Console.WriteLine("Hello, World!")
    End Sub
End Class
In addition to a default constructor, you can define New so it takes any number of arguments. In fact, you can define as many different versions of New as you want for the same object, as long as each has a different function signature. This process is called overloading, and it looks like this:
Public Class New
   
    Public Sub New( )
        Console.WriteLine("Hello, World!")
    End Sub
   
    Public Sub New (name As String)
        Console.WriteLine("Hello, {0}", name)
    End Sub
   
End Class

Section 3.7.1.1: Instances versus references

Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Delegates and Events
You have seen several ways that classes can establish a rapport with one another. A derived class can converse with its parent through Protected methods and a nonderived class can use the Public or Friend (if in the same assembly) methods.
You might have noticed that this level of communication implies a priori knowledge of the classes involved; all classes and their methods are known at compile time. Therefore the lines of communication are drawn, hardcoded into the mix.
Consider the following piece of code:
Public Class Log
   
    Public Sub Write(ByVal msg As String)
        #If Debug Then
        Console.WriteLine(msg)
        #End If
    End Sub
   
End Class
You can imagine that this simple class provides basic debugging capabilities for some application. As you can see, it is wired to write a message to the console. Not very accommodating, is it? What if you wanted to write the message to a text file or database or send it to a remote debugging console? Or worse yet, what if you moved the class to a Windows executable? There wouldn't be a console window anymore.
You could go into the class and add new methods for each scenario, but that wouldn't help much if calls to Log.Write were already scattered throughout your object model. You could always rewrite Log.Write every time you wanted to send the message to a different location, but that's not too practical either. OOP involves code reuse, not code rewrite. That wouldn't exactly lead to a class that you could share with your friends and neighbors.
It would be nice to tell the Log class which method to call in order to do the logging. Then you could switch methods at runtime—perhaps even incorporating several different logging methods: logging to a local text file or sending debug messages to a remote debug window.
For years, C/C++ programmers used callbacks to achieve this capability. In a callback situation, one function receives a pointer to another function through its argument list. The function can then use this pointer to send a notification to the callback. If you could use the pointer in this way with the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Design Considerations
Many books that cover object-oriented design do not focus on any particular language. Some don't even contain one line of code, which shows that knowledge of object design can transcend whatever language you work with.
This chapter mentions several good and not-so-good programming practices, but this discussion is only an introduction to the topic. You should remember a few general rules that will get you started on the right track:
  • Design a class from the user's perspective, not yours. Keep it as simple as possible.
  • Limit scope as much as possible when dealing with classes, methods, and member data. Classes should expose as little as possible through their public interface. Public interfaces should have as few parameters as they can. Member data should always be Private. If it is not, think long and hard about why it isn't. Remember, changes to a class are easier to accommodate if information is hidden.
  • Classes are nouns; methods are verbs. If you find yourself writing a class named Wash, you really might be writing a method that should be in Car.
  • Methods imply behavior; properties imply state. Don't confuse the two concepts.
  • Minimize interactions among classes to reduce complexity in the system.
  • The first abstraction you come up with is probably not the best. If you get an abstraction wrong, you might end up coding yourself into a corner later on and be forced to punch holes in your once-beautiful object hierarchy.
  • Whatever you do, be consistent. A lack of consistency is a lack of style. Develop your style. The people who have to look at your code when you leave will thank you instead of curse you.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
An Exercise
The chapter is "officially" over (wink, wink). The following two listings are only an exercise. Example 3-16 contains a remote debugging console. This console window can run on any computer on your network (or it can run on your only computer if you are processor-challenged). It just sits there, waiting for messages from the RemoteDebug class, which is shown in Example 3-17. To r