Creating the Employee Class

Creating the Employee Class

To get started, open Visual Studio .NET and follow along with these steps:

  1. Create a new project in Visual Basic .NET. Select Visual Basic Projects as the project type and then, because we're going to build our own class, select the Empty Project template, as shown in Figure 3-1. Add the name Employee and the location as C:\VB .NET Coding Techniques\Chap03. Now that we have an empty project, we have to add the features that we want rather than have the integrated development environment (IDE) provide us with a default form.

    Figure 3-1

    Create an empty project.

    It might appear that nothing has happened because the project is empty. Take a look at the menu bar of the IDE, however, and you can see that it says Employee.

  2. Select Project | Add Class to add a class module to the project. Give the new class the name Employee.vb, shown in Figure 3-2, and then click Open to add the empty class declaration to the project.

    Figure 3-2

    Add a class to the project.

    Now we have a single tab in our project, the new Employee.vb class. The Public Class Employee template is added for us, as you can see in Figure 3-3. Delete the contents of the class because we want to add the code ourselves.

    Figure 3-3

    The new class is created from a template.

  3. Replace the existing code in the Employee.vb class module with the following code. This code defines the class, establishing the template for objects created from the class in memory.

    Option Strict On Imports System Namespace employees Public Class Employee Private m_sFirstName As String = "" Private m_sLastName As String = "" Private m_bVBNETTrained As Boolean = False Private m_iEmpNumber As Integer = 0 Private Shared iTotEmployees As Integer = 0 Sub new() MyBase.New() iTotEmployees += 1 m_iEmpNumber = iTotEmployees End Sub Sub new(ByVal fName As String, ByVal lName As String) MyBase.New() m_sFirstName = fName m_sLastName = lName iTotEmployees += 1 m_iEmpNumber = iTotEmployees End Sub Overridable Function Serialize() As Boolean If ((m_sFirstName <> "") And (m_sLastName <> "")) _ Then Return True Else Return False End If End Function #Region "Employee Properties" Public ReadOnly Property EmployeeNumber() As Integer Get Return m_iEmpNumber End Get End Property Property FirstName() As String Get FirstName = m_sFirstName End Get Set m_sFirstName = Value End Set End Property Property LastName() As String Get LastName = m_sLastName End Get Set m_sLastName = Value End Set End Property Property VBNet() As Boolean Get VBNet = m_bVBNETTrained End Get Set m_bVBNETTrained = Value End Set End Property #End Region End Class End Namespace

Examining the Class Code

This class won't run until we implement it in a program that creates a new instance of the class, but let's examine how the class is constructed so that we understand each of its components. A bit later in the chapter, we'll build a test harness to demonstrate how to actually create and use an instance of the class.

The first step we take is to import the System namespace. As you know, every class is derived from the root namespace, System, which is where System.Object lives. Our class module, like other modules, can contain any number of Imports statements. Remember that Imports statements must occur in a module before any references to identifiers within the module, so you always want to place these statements at the beginning of a module. The only code that would go before an Imports statement is the Option Strict or Option Explicit directive. I'll cover these statements in more detail in Chapter 4, "Visual Basic .NET Data Types and Features."

Adding a Reference to Our Project

Because we didn't create this project as a Windows application but as an empty project, Visual Basic .NET has no idea what we are going to import. We don't need to clutter an empty project with default references that might not be used, so no references are added when we start. Take a look at the Solution Explorer, shown in Figure 3-4. You'll see that the references structure is empty. (If the Solution Explorer is not visible, click View | Solution Explorer.)

Figure 3-4

The project contains no references.

Just putting an Imports statement at the start of the code is not enough. We have to add the Visual Basic .NET Framework references that the project will use. We also have to add a reference to the project's assembly so that Visual Basic .NET can find it. Adding a reference to a Visual Basic .NET project is similar to adding one to a project in classic Visual Basic. For example, if you wanted to create a reference to an Excel workbook to do some Office Automation in classic Visual Basic, you would create the Excel object. However, classic Visual Basic had no idea what you were talking about until a reference to the Microsoft Excel Workbook Type Library was added.

The designers of Visual Basic 6 had to make some difficult decisions about what to include in the language and what could be added later on when needed. For example, if you've ever used Win32 API calls in Visual Basic or even imported a reference to the Excel Type Library, you know that these capabilities basically extend the base language. You can perform operations with API calls that are not possible with only Visual Basic. However, the designers of Visual Basic didn't need to include all the functionality contained in the hundreds of API calls, or even those used when referencing an Excel spreadsheet. All of this functionality could easily be accessed by including API function definitions or adding a reference to the Excel Type Library to our code. The designers of Visual Basic .NET faced the same design issues. Importing all namespaces in all assemblies into our .NET programs isn't necessary. We need to import (or reference) only those namespaces that contain classes useful for the problem at hand.

  1. Right-click References to display the Add Reference menu option, shown in Figure 3-5.

    Figure 3-5

    Adding a reference in the Solution Explorer.

    Selecting Add Reference displays a tabbed dialog box that lists each of the .NET Framework namespaces, shown in Figure 3-6. Each reference we add is a reference to an assembly. As you know, each assembly lives in its own dynamic-link library (DLL) in a physical file on disk. The System namespace assembly, for example, is really System.dll.

    Figure 3-6

    Each reference is a DLL file on disk.

  2. Double-click System.dll in the Component Name column to add it to the Selected Components list. Click OK to add this reference to the project.

tip

Many times you'll want to use the Imports statement for an assembly. You write the code, but the IDE tells you that the particular reference is not defined because the reference to the assembly you wanted to import was not added to the Solution Explorer. Default references usually do the job, but not always. Be sure to peek at the references in the Solution Explorer to ensure that your program knows how to find the particular assembly that contains the classes you want to import and use.

The Solution Explorer should now look like Figure 3-7. It shows the Employee class and a reference to a .NET namespace.

Figure 3-7

The reference to Employee.vb.

DLLs in .NET

For those classic Visual Basic programmers among you, the concept of a DLL can be illustrated by thinking of a graphical control. For example, you might have added a Bound DataGrid or a calendar control to your tool palette. All classic Visual Basic controls had the extension .ocx, but they were really DLLs with a different name. Once you had a calendar control on the toolbox, you could create as many calendars as you needed from the single control on the palette. You might have had a form that had six separate calendars on it. While each calendar object had its own name and could be programmed independently, only a single instance of the calendar was stored in an OCX file. Each time you placed another calendar control on a form, a new instance was created from the single calendar class that lived in the OCX.

The .NET Framework operates in the same way. For example, if you right-click the toolbox and select Customize Toolbox, the dialog box shown below pops up. Selecting the .NET Framework Components tab reveals that all the graphical controls still live in DLLs. If you add a reference to a check box control, you can see that it lives in System.Windows.Forms.DLL. You can draw as many check boxes on your form as you need, but they all come from a single class that lives in a single DLL. Under the hood, you are simply creating multiple instances from the same class.

That's the beauty of object-oriented programming. The Holy Grail of modern programming can be summed up in a single word: reusability. Because we can modularize our program into classes, each becomes a self-contained unit. This segregation makes each unit much easier to debug. Once you have a fully functioning and debugged class, you can put it aside knowing that that part of your program works fine. By generalizing our code, we can reuse the classes we build ourselves, dramatically speeding up development of subsequent programs. When your program references a DLL, the functionality that lives in the file is available to another program. This expands the language tremendously. Using DLLs is like getting a cornucopia of functionality free of charge, and the functions operate just as fast as if they were native to the program calling them.

Our Class's Namespace

Notice that our code uses a namespace named employees. Remember that a namespace declaration enables you to group related types into a hierarchical categorization. The entire Employee class is contained in the employees namespace. While our simple program has only a single class in the namespace, you will usually include much larger programs within a namespace.

The purpose of using a unique namespace construct is to prevent what is known as a namespace collision. For example, let's say that both you and I create a class named Employee. Having two classes with the same name could be very confusing to a program attempting to use a specific Employee class. However, when we add a namespace, we change the name of our class to namespace.classname, and this naming scheme makes the names of our classes unique. When we want to use our Employee class, we reference it by calling employees.Employee.

Microsoft recommends using the names of your company and products as nested namespaces. For example, if I worked for Litware, Inc., and our Employee class was used in a human resource software program named EmpTracker, I would create a namespace such as Namespace Litware.EmpTracker.

Someone needing to use this class would have to reference it by using a statement such as the following:

Dim newEmp1 As New Litware.EmpTracker.Employee

Although not foolproof, in most cases this naming convention will fully qualify the specific Employee class I want to use. Unless someone duplicates the name of my company, the name of my product, and the name of my class, we can be pretty sure the right class will be called. When you are writing production code, always use this namespace naming scheme.

Declaring Our Class

Next the class itself is declared, sandwiched between the Namespace and End Namespace statements. Our class is declared using the Public access modifier so that it is available to all procedures in all classes, modules, and structures in all applications that might need to use it.

Public Class Employee

When we want to store information about a new employee, we simply create a new object for that employee. Of course, when we create an instance of our Employee class by using the keyword New, the instance is created in memory and at that point becomes an object.

Each individual instance of our Employee class will hold specific information about an individual employee, such as the employee's first name and last name, whether the employee is trained in Visual Basic .NET, and the employee's unique identification number. (Our company might have ten John Smith's working for it, and because each John Smith is different, using just the name won't be enough. In a real application, we might use an employee's Social Security number to fully qualify each employee.)

Private m_sFirstName As String = "" Private m_sLastName As String = "" Private m_bVBNETTrained As Boolean = False Private m_iEmpNumber As Integer = 0

The variables used to hold this information—m_sFirstName, m_sLastName, m_bVBNETTrained, and m_iEmpNumber—are declared as Private. These variables are known as a class's member fields, or data elements. These variables are declared as private, so they are available only to the class, module, or structure in which they are declared. You'll recall from Chapter 2 that this level of access means that they can't be read or written to from outside the class unless we provide a property with which to do so.

Properties allow us to control the values that are entered into or read from the classes' fields from the outside world. For example, we might want to check data to ensure that it's numeric before assigning that data to a numeric class data field. We also probably want to edit or perform reasonability checks on all incoming values. Properties are just the place to do this.

note

Notice that the private variables are prefixed with m_. By convention, this prefix designates that the variables are class (m)ember data fields. Each variable also has a descriptive name so that readers of the code know exactly what the members are used for. I like to preface each name with a single character that tells the reader the data type of the variable. When I see m_sFirstNamein the code, it's immediately apparent that this is a member variable, so it's private and a member of the class. The s tells me it's a string, and, of course, the descriptive name of the variable is self-documenting.

By keeping the member variables private to the class, we make use of data encapsulation, or data hiding, one of the important object-oriented programming concepts introduced in Chapter 2. The ability to hide data ensures the integrity of the class's data elements.

tip

I'd like to point out once more the capability of Visual Basic .NET to initialize variables on the same line as they are declared. The language will provide a default value of 0 for integers and a default value of " " for strings. However, in keeping with our professional programming approach, we are never going to rely on the default behavior of the language. Making the default value explicit is good programming practice because it enhances code readability, ensures the programmer knows precisely what the value will be, and does not leave the initial value to chance.

Using Shared Variables

The next line of code in our class is an example of a shared variable. Remember that when the Shared keyword is used, a single copy of the variable is shared between all objects of type Employee.

Private Shared iTotEmployees As Integer = 0

You can think of a shared variable in the same way you think of global variables. The only difference is that a shared variable is seen and shared only by members of the same class. Because each employee will have a unique iEmpNumber, we will use the shared variable iTotEmployees to keep track of how many employees are currently assigned. Our code will increment the shared iTotEmployees variable each time a new employee object is created. That number will be assigned to the new employee object's iEmpNumber. The shared variable iTotEmployees can be seen by all instances of the Employee class, while an individual instance of the class can only see the individual employee's m_iEmpNumber.

Class Constructors

When another programmer wants to create a new Employee object from our Employee class, he or she uses the New keyword. To create a new Employee object, you would use the New keyword like this:

Dim newEmp1 As New employees.Employee()

When the compiler encounters this Dim statement, it creates a new instance of the Employee class and uses the reference variable newEmp1 to communicate with the object. The programmer can now work with the newly created object by using newEmp1.

In classic Visual Basic, this way of using New in the dimensioning statement was misleading. The class was not actually instantiated; the statement simply reserved space for the object variable at compile time. Even though you used New, the object variable was still initialized to Nothing. The object wasn't actually created in memory until you touched the object for the first time with an operation such as setting a property. Visual Basic .NET works the way Visual Basic 6 should have. When you use the New keyword, an object is created at that time. This mechanism is much more intuitive and is similar to object creation in other OOP languages.

When the New keyword is encountered by the compiler, the class is instantiated as an object by calling its constructor, New. Class constructors initialize instances of a class and are run by the common language runtime environment when an instance is created.

Sub New()     MyBase.New()     iTotEmployees += 1     iEmpNumber = iTotEmployees End Sub

The New and Destruct procedures in Visual Basic .NET replace the Class_Initialize and Class_Terminate methods used in classic Visual Basic to initialize and destroy objects. However, unlike Class_Initialize, the New constructor will run only once when a class is created. It cannot be called explicitly anywhere other than in the first line of code in another constructor from either the same class or from a derived class. The code in the New method will always run before any other code in a class. And, unlike classic Visual Basic, Visual Basic .NET permits you to pass parameters into a constructor. Not only that, the constructor can also be overloaded in .NET.

Overloading Constructors

Instead of simply creating a New constructor in the way we just did, it might be helpful to create additional overloaded constructors to illustrate the concept of passing parameters to a constructor. Notice that we do not have to use the access modifier Overloads for our constructors. The compiler is smart enough to recognize that when it sees two New procedures in a class, overloaded constructors are around. However, each New must have a signature (the name of the procedure and the parameter list) that is different in the number or type of arguments. Simply making one parameter ByVal and an identical parameter ByRef does not work. Because our first constructor's signature does not take any parameters (the pair of parentheses indicate it's empty), none of the private member variables, such as name or age, can be given values other than the default initialization.

The following constructor signatures allow you to create a new Employee class object by calling the constructor using either no parameters or with the parameters for first and last names. The compiler will select the correct overloaded constructor with which to create an Employee object on the basis of the signature you use.

Sub New() Sub New(ByVal fName As String, ByVal lName As String)

In practice, overloading is useful when your object class dictates that you use similar names for procedures that operate on different data types. For example, in the Employee class we are passing in strings. You can see how we could overload a constructor to pass in either an employee's name or Social Security number. Visual Basic .NET knows how to call the correct constructor by examining the parameters and matching them with the correct constructor.

Overloading is also useful in procedures in which you would previously use the Optional keyword or a parameter array to pass in a variable number of parameters. In those cases, extra code had to be written to determine whether any optional parameters were indeed passed. If a param array was used, more code had to be written to determine the number and type of parameters passed in. Overloading makes passing in a variable number and type of parameters much cleaner and simpler.

MyBase.New

MyBase.New must be the first line of code in the constructor. This statement calls the base-class constructor of the class from which our class is derived, initializing the base class that our class inherits from. It also performs implicit variable initializations. (The keyword MyBase refers to a class's immediate base class and allows a class to call methods in its base class.) Because we are not explicitly inheriting from a class in this example, we implicitly inherit from the Object class, the ultimate base class for all .NET objects.

If our class is derived from another class, using MyBase would call the parent class's constructor. If that class was derived from another class, its constructor would be called by our parent class's constructor and so on up the chain to ensure that the topmost class was initialized first. The code would then return down the sequence and initialize all the parent classes in turn before it got to ours. This sequence ensures that any classes that our class inherits from are fully initialized before our class tries to use them.

While the Windows form we used in Chapter 2 had inherited members, our Employee class will have only methods we define (for example, New), as well as the methods inherited from the System.Object class. MyBase is generally used to access public members defined in the base class. MyBase cannot, however, be used to access any private members in the base class. MyBase cannot be assigned to a variable or passed to procedures because MyBase is not a real object. Finally, MyBase can't be used in modules, only in classes.

Assigning Values to Our Private Data Fields

We want to hide the implementation of how the employee number, m_iEmpNumber, is assigned from the outside world. Because the assignment of the new employee number should be automatic, it can be completed in the constructor. Upon creation of an Employee object, the iTotEmployees shared variable is incremented by 1 using the new streamlined += syntax. Of course, iTotEmployees is visible to all instances of the Employee class because it is shared.

The new value of iTotEmployees is then immediately assigned to the specific class's private variable, m_iEmpNumber.

Sub New()     MyBase.New()     iTotEmployees += 1     m_iEmpNumber = iTotEmployees End Sub

In the overloaded constructor in which we pass in values, these values are immediately assigned to the Employee class member data fields for the employee name.

Sub New(ByVal fName As String, ByVal lName As String)     m_sFirstName = fName     m_sLastName = lName     iTotEmployees += 1     m_iEmpNumber = iTotEmployees End Sub

Notice that the two parameters in the overloaded constructor are passed in ByVal(ue) instead of ByRef(erence). When a parameter is passed in ByVal, a copy of the original is passed in, not the original itself. The procedure can modify the value passed in, but the original value remains intact. On the other hand, if the parameter is passed in ByRef, a reference to the memory location is passed in, which means that any change made to the parameter within the procedure changes the original value. While ByRef is typically faster, it can cause unintended side effects and can make tracking down bugs difficult. I'll describe ByVal and ByRef in more detail in Chapter 4. I'll also highlight situations in which you want to use ByRef later in the book; it's usually desirable to use ByVal.

note

In Visual Basic 6, if you do not specify ByVal or ByRef for a procedure parameter, the passing mechanism defaults to ByRef, which means that if the variable is modified by the called procedure it is also modified in the calling program. In Visual Basic .NET, the passing mechanism defaults to ByVal for every parameter when you declare a procedure. This protects parameters against modification.

Overriding

As you've learned, derived classes inherit the methods defined in their base class. All methods carry the NotOverridable keyword by default. If a method in a base class is marked with the Overridable keyword, however, you can use the Overrides keyword in a derived class to override the base class's method with your own.

Here's another instance of when to make explicit what Visual Basic .NET does by default. If you don't want any classes that inherit our Employee class to ever override a method, you would use the NotOverridable keyword to qualify the method. In practice this keyword is rarely used because it's redundant. However, when you don't want a method to be overridden, follow good programming form and use the NotOverridable keyword. Not only does this keyword protect you from how the default behavior of a method might change down the road, but it also makes your intention crystal clear.

Let's see how overriding is used in our Employee class example. If we create 10 employee objects and add data to their member variables, our labor will do us little good if we can't then save the information to a physical storage medium such as a disk file. We want to persist the objects by saving them so that we can read them back into memory later. Serializing the objects to disk in a binary format will save all the data we entered in our objects.

The great news is that Visual Basic .NET handles serialization for us through built-in framework classes. However, we don't want to save Employee objects that don't have critical information such as an employee's first or last name. By adding a simple function, named Serialize, we can return a Boolean True or False after we determine whether the name fields are populated.

We also might want to create another more specialized class sometime in the future that inherits from Employee. For example, we might want to create a Division class that inherits from the Employee class and has member data fields that need to be interrogated to ensure that they contain data before saving the object.

Because Visual Basic .NET methods are not overridable by default, you must explicitly use the keyword Overridable for those methods you want derived classes to be able to override. This designation permits us to either use the method as-is in this class or override it in a child class. Overridden methods are often used to implement polymorphism, which, you'll remember, means many forms. In object-oriented programming, the behavior of a polymorphic method depends on the object being called.

Overridable Function Serialize() As Boolean     If ((m_sFirstName <> "") And (m_sLastName <> "")) Then         Return True     Else         Return False     End If End Function

Several aspects of this function are worth noting. First, while we implement Serialize as a function, to the outside world it's a method. Within our class, we implement methods by using either subroutines or functions. The difference between a subroutine and a function is, of course, that a function returns a value whereas a subroutine does not.

Code that created an instance of our Employee class could now do a quick check by including the following statements:

If (MyEmployee.Serialize = True) then     'Do something important End If

When a value is returned to the caller, the new Visual Basic .NET keyword Return is used. You can still use the Visual Basic 6 syntax for assigning the return value to the name of the function: Serialize = True. Both return syntaxes work fine.

The final item to note is the use of parentheses. While parentheses are not required to make the program run, the use of them is a habit I developed way back in grad school. Spending a second or two to type them in is a good idea for a few reasons.

First, with parentheses, the comparisons being made aren't ambiguous, which enhances readability by orders of magnitude, especially for complex comparisons. When you are performing numeric comparisons, you use parentheses to order the comparisons, of course. In our example, a purist might say that using parentheses is marginally expensive in processing time. However, the time lost is negligible when compared to the absolute readability the parentheses provide. A quick glance at this line leaves no doubt about exactly what is being examined. Using parentheses is a professional touch that ensures you are comparing what you think you are comparing.

If ((m_sFirstName <> "") And (m_sLastName <> "")) Then 

#Region

Remember that when we added the class properties, we sandwiched them between the #Region / #End Region statements. #Region takes a single string parameter that is used to describe what code is hidden.

#Region "Employee Properties"  #End Region

Regions are another great feature of the .NET IDE. When programming in classic Visual Basic, I used the procedure view all the time to isolate just the code I was working on. The #Region feature provides the same functionality—any code placed within a region can be collapsed or expanded so that the workspace isn't cluttered. In Figure 3-8, you can see that the Employee Properties region is currently collapsed. Clicking the plus sign expands the code for the properties. The ability to place custom text such as Employee Properties in a region to let us know what's included makes working with huge chunks of code a breeze.

Figure 3-8

The Employee Properties region is collapsed.



Coding Techniques for Microsoft Visual Basic. NET
Coding Techniques for Microsoft Visual Basic .NET
ISBN: 0735612544
EAN: 2147483647
Year: 2002
Pages: 123
Authors: John Connell

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net