Chapter 8. Working with Objects

Classes are practically synonymous with objects and they're at the very heart of programming with Visual Basic. The controls you use to build the visible interface of your application are objects, and the process of designing forms consists of setting the properties of these objects, mostly with point-and-click operations. The Framework itself is an enormous compendium of classes, and you can import any of them into your applications and use them as if their members were part of the language. You simply declare a variable of the specific class type, initialize it, and then use it in your code.

Controls are also objects; they differ from other classes in that controls provide a visual interface, whereas object variables don't. However, you manipulate all objects by setting their properties and calling their methods.

In this chapter, you'll learn how to do the following:

  • Build your own classes

  • Use custom classes in your projects

  • Customize the usual operators for your classes

Classes and Objects

When you create a variable of any type, you're creating an instance of a class. The variable lets you access the functionality of the class through its properties and methods. Even base data types are implemented as classes (the System.Integer class, System.Double, and so on). An integer value, such as 3, is an instance of the System.Integer class, and you can call the properties and methods of this class by using its instance. Expressions such as Convert.ToDecimal(3).MinValue and #1/1/2000#.Today are odd but valid. The first expression returns the minimum value you can represent with the Decimal data type, whereas the second expression returns the current date. The DataTime data type exposes the Today property, which returns the current date. The expression #1/1/2000# is a value of the DataTime type, so you can find out the current date by calling its Today property. If you enter either one of the preceding expressions in your code, you'll get a warning, but they will be executed.

Classes are used routinely in developing applications, and you should get into the habit of creating and using custom classes, even with simple projects. In team development, classes are a necessity because they allow developers to share their work easily. If you're working in a corporate environment, where different programmers code different parts of an application, you can't afford to repeat work that someone else has already done. You should be able to get their code and use it in your application as is. That's easier said than done because you can guess what will happen as soon as a small group of programmers start sharing code — they'll end up with dozens of different versions for each function, and every time a developer upgrades a function, they will most likely break the applications that were working with the old version. Or each time they revise a function, they must update all the projects by using the old version of the function and test them. It just doesn't work.

The major driving force behind object-oriented programming (OOP) is code reuse. Classes allow you to write code that can be reused in multiple projects. You already know that classes don't expose their source code. The Framework itself is a huge collection of classes, which you can use without ever seeing its source code. As you'll learn in Chapter 10, "Applied Object-Oriented Programming," you can even expand the functionality of an existing class without having access to its code. In other words, you can use a class without having access to its code, and therefore you can't affect any other projects that use the class. You also know that classes implement complicated operations and make these operations available to programmers through properties and methods. The Array class, for example, exposes a Sort method, which sorts its elements. This is not a simple operation, but fortunately you don't have to know anything about sorting. Someone else has done it for you and made this functionality available to your applications. This is called encapsulation. Some functionality has been built into the class (or encapsulated into the class), and you can access it from within your applications by using a simple method call. The System.Security.Cryptography class of the Framework (which isn't discussed in this book) provides all the functionality you need to encrypt a secret code, or an entire document, by calling a method. Encryption is a very complicated operation, but you don't have to know anything about it except how to call the appropriate method and pass a secret key to it.

What Is a Class?

A class can be thought of as a program that doesn't run on its own; it's a collection of properties and methods that must be used by another application. We exploit the functionality of the class by creating a variable of the same type as the class and then calling the class's properties and methods through this variable. The methods and properties of the class, as well as its events, constitute the class's interface. It's not a visible interface, like the ones you've learned to design so far, because the class doesn't interact directly with the user. To interact with the class, the application uses the class's interface, just as users will be interacting with your application through its visual interface.

You have already learned how to use classes. Now is the time to understand what goes on behind the scenes when you interact with a class and its members. Every object is an instance of a class. When you declare an array, a Color object, or a collection, some code is executed in the background to create the variable. It's the code that actually implements the class. When you declare an array, you're invoking the System.Array class, which contains all the code for manipulating arrays (the method to sort the array's elements, another method to reverse the order of the elements in the array, and so on). Even simple variables of the Integer or String type are implemented as classes.

The first time you use an object in your code, you're instantiating the class that implements it. The class's code is loaded into memory, initializes its variables, and is ready to execute. The image of the class in memory is said to be an instance of the class, and this is an object.

At the beginning of this section I mentioned that classes can be thought of as "programs that can't be executed on their own." This is an oversimplification, which I can remedy now. Classes are made up of code, not visual elements, and are used as templates for objects. For example, there's a single System.Array class and this class is invoked every time you create an array in your code. The arrays you declare in your code are instances of the class. Yet, there's only one class that implements arrays and all arrays are "serviced" by the same class.

Classes Combine Code with Data

Another way to view classes is to understand how they combine code and data. This simple idea is the very essence of object-oriented programming. Data is data, and procedural languages allow you to manipulate data in any way. Meaningful data, however, is processed in specific ways.

Let's consider accounting data. You can add or subtract amounts to or from an account, sum similar accounts (such as training and travel expenses), calculate taxes on certain account amounts, and the like. Other types of processing may not be valid for this type of data. You never multiply the amounts from two different accounts or calculate logarithms of account balances. These types of processing are quite meaningful with different data, but not with accounting data.

Because the nature of the data itself determines to a large extent the type of processing that will take place on the data, why not "package" the data along with the code for processing it? Instead of simply creating structures for storing our data, we also write the code to process them. The data and the code are implemented in a single unit, a class, and the result is an object. After the class has been built, we no longer write code for processing the data; we simply create objects of this type and call their methods. To transfer an amount from one account to another, we call a method that knows how to transfer the amount, and the same method also makes sure the amount isn't subtracted from one account unless it has been added to the other account (and vice versa). By the way, the process of completing multiple operations in a single step or canceling all the operations if one of them fails is known as a transaction. A transaction will not subtract an amount from one account unless it has added the same amount to another account, and it won't credit an account without debiting another one by the same amount. You'll learn more about transactions later in this book, when we'll explore database programming.

To better understand how classes combine code with data, let's take a close look at a class we're all too familiar with, the Array class. The role of the array is to store sets of data. In addition to holding data, the Array class also knows how to process data: how to retrieve an element, how to extract a segment of the array, and even how to sort its elements. All these operations require a substantial amount of code. The mechanics of storing data in the array, the code that implements the properties, and the methods of the array are hidden from you, the developer. You can instruct the array to perform certain tasks by using simple statements. When you call the Sort method, you're telling the array to execute some code that will sort its elements. As a developer, you don't know how the data are stored in the array or how the Sort method works. An overloaded form of the method allows you to sort a segment of the array by specifying the index of the first and last elements to be sorted. All you have to know is how to call the Sort method, not how it works. Classes abstract many operations by hiding the implementation details; developers manipulate arrays by calling methods. And you certainly can't access the code of the class and edit it to accommodate the requirements of a specific application.

With LINQ, a new technology for querying collections that was introduced with version 3.5 of the Framework, the Array class was enhanced with a few new methods, like the Sum method that calculates the sum of the elements in a numeric array and the Select method that allows you to select elements that meet certain criteria, and a few more. You'll learn a lot more about these methods (they're called extension methods) in Chapter 14, "An Introduction to LINQ." Although this is something you'll understand better in Chapter 14, let me just mention that the team that implemented LINQ did not have access to the source of the Array class!

In the following sections, you'll learn how data and code coexist in a class and how you can manipulate the data through the properties and methods exposed by the class. In Chapter 3, "Visual Basic Programming Essentials," you learned how to create structures to store data. Classes are similar to structures in that they represent custom data structures. In this chapter, I'll take the idea of defining custom data structures one step further, by adding properties and methods for manipulating the custom data, something you can't do with structures. Let's start by building a custom class and then using it in an application.

Building the Minimal Class

My first example is the Minimal class; I'll start with the minimum functionality class and keep adding features to it. The name of the class can be anything — just make sure that it's suggestive of the class's functionality.

A class may reside in the same file as a form, but it's customary to implement custom classes in a separate module, a Class module. You can also create a Class project, which contains one or more classes. However, a class doesn't run on its own and you can't test it without a form. Start a new Windows project and name it SimpleClass (or open the SimpleClass sample project available for download from www.sybex.com/go/masteringvb2010). Then create a new class by adding a Class component to your project. Right-click the project name in the Solution Explorer window and choose Add

Building the Minimal Class
Adding a class item to a project

Figure 8.1. Adding a class item to a project

The sample project contains a main form, as usual, the Form1 form. The code that implements the class resides in the Minimal.vb file, which is part of the project, and you'll use the project's main form to test your class. After you have tested and finalized the class code, you no longer need the form and you can remove it from the project.

When you open the class by double-clicking its icon in the Project Explorer window, you will see the following lines in the code window:

Public Class Minimal

End Class

If you'd rather create a class in the same file as the application's form, enter the Class keyword followed by the name of the class after the existing End Class in the form's code window. The editor will insert the matching End Class for you. Insert a class definition in the form's code window if the class is specific to this form only and no other part of the application will use it. At this point, you already have a class, even if it doesn't do anything.

Switch back to the Form Designer, add a button to the test form, and insert the following code in its Click event handler:

Dim obj1 As Minimal

Press Enter and type the name of the variable, obj1, followed by a period, on the following line. will see a list of the methods your class exposes already:

Equals
GetHashCode
GetType
ReferenceEqual
ToString

If you don't see all of these members, switch to the All Members tab of the IntelliSense drop-down box.

These methods are provided by the Common Language Runtime (CLR), and you don't have to implement them on your own (although you will probably have to provide a new, nongeneric implementation for some of them). They don't expose any real functionality; they simply reflect the way VB handles all classes. To see the kind of functionality that these methods expose, enter the following lines in the Button's Click event handler and then run the application:

Dim obj1 As New Minimal
Debug.WriteLine(obj1.ToString)
Debug.WriteLine(obj1.GetType)
Debug.WriteLine(obj1.GetHashCode)
Dim obj2 As New Minimal
Debug.WriteLine(obj1.Equals(obj2))
Debug.WriteLine(Minimal.ReferenceEquals(obj1, obj2))

The following lines will be printed in the Immediate window:

SimpleClass.Minimal
SimpleClass.Minimal
18796293
False
False

The name of the object is the same as its type, which is all the information about your new class that's available to the CLR. Shortly you'll see how you can implement your own ToString method and return a more-meaningful string. The hash value of the obj1 variable is an integer value that uniquely identifies the object variable in the context of the current application. (It happens to be 18796293, but the actual value is of no consequence for our discussion. It's a standard member, but it's good to know that the functionality is built into the control.)

The next line tells you that two variables of the same type are not equal. But why aren't they equal? We haven't differentiated them at all, yet they're different because they point to two different objects, and the compiler doesn't know how to compare them. All it can do is figure out whether the variables point to the same object. If you want to understand how objects are compared, add the following statement after the line that declares obj2:

obj2 = obj1

When you run the application again, the last two statements will display True in the Output window. The Equals method compares the two objects and returns a True/False value. Because you haven't told the class how to compare two instances of the class yet, it compares their references, just as the ReferenceEquals method does. The ReferenceEquals method checks for reference equality; that is, it returns True if both variables point to the same object (the same instance of the class). If you change a property of the obj1 variable, the changes will affect obj2 as well, because both variables point to the same object. We can't modify the object because it doesn't expose any members that we can set to differentiate it from another object of the same type. We'll get to that shortly.

Most classes expose a custom Equals method, which knows how to compare two objects of the same type (two objects based on the same class). The custom Equals method usually compares the properties of the two instances of the class and returns True if a set of basic properties (or all of them) are the same. You'll learn how to customize the default members of any class later in this chapter.

Notice the full name of the class: SimpleClass.Minimal. Within the current project, you can access it as Minimal. Other projects can either import the Minimal class and access it as Minimal or specify the complete name of the class, which is the name of the project it belongs to followed by the class name. To use the Minimal class in another project, however, you must add a reference to it. (You'll learn shortly how to reuse classes in other projects.)

Adding Code to the Minimal Class

Let's add some functionality to our bare-bones class. We'll begin by adding two trivial properties and two methods to perform simple operations. The two properties are called strProperty (a string) and dblProperty (a double). To expose these two members as properties, you can simply declare them as public variables. This isn't the best method of implementing properties, but it really doesn't take more than declaring something as public to make it available to code outside the class. The following statement exposes the two properties of the class:

Public strProperty As String, dblProperty As Double

The two methods you'll implement in your sample class are the ReverseString and NegateNumber methods. The first method reverses the order of the characters in strProperty and returns the new string. The NegateNumber method returns the negative of dblProperty. They're two simple methods that don't accept any arguments; they simply operate on the values of the properties. Methods are exposed as public procedures (functions or subroutines), just as properties are exposed as public variables. Enter the function declarations of Listing 8.1 between the Class Minimal and End Class statements in the class's code window. (I'm showing the entire listing of the class here.)

Example 8.1. Adding a few members to the Minimal class

Public Class Minimal
      Public strProperty As String, dblProperty As Double
      Public Function ReverseString() As String
          Return (StrReverse(strProperty))
      End Function
      Public Function NegateNumber() As Double
          Return (-dblProperty)
      End Function
  End Class
The members of the class are displayed automatically by the IDE, as needed.

Figure 8.2. The members of the class are displayed automatically by the IDE, as needed.

Let's test the members we've implemented so far. Switch back to your form and enter the lines shown in Listing 8.2 in a new button's Click event handler. The obj variable is of the Minimal type and exposes the public members of the class, as shown in Figure 8.2. You can set and read its properties and call its methods. Your code doesn't see the class's code, just as it doesn't see any of the built-in classes' code. You trust that the class knows what it is doing and does it right.

Example 8.2. Testing the Minimal class

Dim obj As New Minimal
  obj.strProperty = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  obj.dblProperty = 999999
  Debug.WriteLine(obj.ReverseString)
  Debug.WriteLine(obj.NegateNumber)

Using Property Procedures

The strProperty and dblProperty properties will accept any value as long as the type is correct and the value of the numeric property is within the acceptable range. But what if the generic properties were meaningful entities, such as email addresses, ages, or zip codes? We should be able to invoke some code to validate the values assigned to each property. To do so, we implement each property as a special type of procedure: the so-called property procedure.

Properties are implemented with a special type of procedure that contains a Get and a Set section (frequently referred to as the property's getter and setter, respectively). The Set section of the procedure is invoked when the application attempts to set the property's value; the Get section is invoked when the application requests the property's value. The value passed to the property is usually validated in the Set section and, if valid, is stored to a local variable. The same local variable's value is returned to the application when it requests the property's value, from the property's Get section. Listing 8.3 shows what the implementation of an Age property might look like.

Example 8.3. Implementing properties with property procedures

Private m_Age As Integer
  Property Age() As Integer
     Get
        Age = m_Age
     End Get
     Set (ByVal value As Integer)
        If value < 0 Or value >= 100 Then
           MsgBox("Age must be positive and less than 100")
        Else
           m_Age = value
        End If
     End Set
  End Property

The local variable where the age is stored is m_Age. When a statement such as the following is executed in the application that uses your class, the Set section of the property procedure is invoked:

obj.Age = 39

Because the property value is valid, it is stored in the m_Age local variable. Likewise, when a statement such as the following one is executed, the Get section of the property procedure is invoked, and the value 39 is returned to the application:

Debug.WriteLine(obj.Age)

The value argument of the Set segment represents the actual value that the calling code is attempting to assign to the property. The m_Age variable is declared as private because we don't want any code outside the class to access it directly. The Age property is, of course, public so that other applications can set it, and external applications shouldn't bypass the validation performed by the property's setter.

Enter the property procedure for the Age property in the Minimal class and then switch to the form to test it. Open the button's Click event handler and add the following lines to the existing ones:

Dim obj As New Minimal
obj.Age = 39
Debug.WriteLine("after setting the age to 39, age is " &
                      obj.Age.ToString)
obj.Age = 199
Debug.WriteLine("after setting the age to 199, age is " &
                       obj.Age.ToString)

The value 39 will appear twice in the Output window, which means that the class accepts the value 39. When the third statement is executed, a message box will appear with the error's description:

Age must be positive and less than 100

The value 39 will appear in the Output window again. The attempt to set the age to 199 failed, so the property retains its previous value. You will also see the message box with the warning, which is invoked from within the class's code.

Throwing Exceptions

The error-trapping code works fine, but what good is a message box displayed from within a class? As a developer using the Minimal class in your code, you'd rather receive an exception and handle it from within your code, unless you're writing classes to use in your own applications. Normally, you don't know who's going to use your class, or how, so you can't assume that any messages displayed from within your class's code will be seen by the end user. The class may be invoked on a remote server, in which case the error message will go unnoticed. So let's change the implementation of the Age property a little. The property procedure for the Age property (Listing 8.4) throws an InvalidArgument exception if an attempt is made to assign an invalid value to it. The InvalidArgument exception is one of the existing exceptions, and you can reuse it in your code. Later in this chapter, you'll learn how to create and use custom exceptions.

Example 8.4. Throwing an exception from within a property procedure

Private m_Age As Integer
  Property Age() As Integer
     Get
        Age = m_Age
     End Get
     Set (ByVal value As Integer)
        If value < 0 Or value >= 100 Then
           Dim AgeException As New ArgumentException()
           Throw AgeException
        Else
           M_Age = value
        End If
     End Set
  End Property

You can test the revised property definition in your application; switch to the test form, and enter the statements from Listing 8.5 in a new button's Click event handler. (This is the code behind the Handle Exceptions button on the test form.)

Example 8.5. Catching the Age property's exception

Dim obj As New Minimal
Dim userAge as Integer
UserAge = InputBox("Please enter your age")
Try
   obj.Age = userAge
Catch exc as ArgumentException
   MsgBox("Can't accept your value, " & userAge.ToString & VbCrLf &
          "Will continue with default value of 30")
   obj.Age = 30
End Try

This is a much better technique for handling errors in your class. The exceptions can be intercepted by the calling application, and developers using your class can write robust applications by handling the exceptions in their code. When you develop custom classes, keep in mind that you can't handle most errors from within your class because you don't know how other developers will use your class.

Implementing Read-Only Properties

Let's make our class a little more complicated. Age is not usually requested on official documents because it's valid only for a year (or less) after filling out a questionnaire. Instead, you are asked to furnish your date of birth, from which your current age can be calculated at any time. We'll add a BDate property in our class and make Age a read-only property.

To make a property read-only, you simply declare it as ReadOnly and supply code for the Get procedure only. Revise the Age property code in the Minimal class to that shown in Listing 8.6. Then, enter the property procedure from Listing 8.7 for the BDate property.

Example 8.6. Implementing a read-only property

Private m_Age As Integer
ReadOnly Property Age() As Integer
   Get
      Age = m_Age
   End Get
End Property

Example 8.7. The BDate property

Private m_BDate As DateTime
Private m_Age As Integer
Property BDate() As DateTime
    Get
        BDate = m_BDate
    End Get
    Set(ByVal value As Date)
        If value > Now() Or _
                DateDiff(DateInterval.Year, value, Now()) >= 100 Then
            Dim AgeException As New Exception
                     ("Can't accept the birth date you specified")
            Throw AgeException
        Else
            m_BDate = value
            m_Age = DateDiff(DateInterval.Year, value, Now())
        End If
    End Set
End Property

As soon as you enter the code for the revised Age property, two error messages will appear in the Error List window. The code in the application form is attempting to set the value of a read-only property, so the editor produces the following error message twice: Property 'Age' is 'ReadOnly.' As you probably figured out, we must set the BDate property in the code instead of the Age property. The two errors are the same, but they refer to two different statements that attempt to set the Age property.

The code checks the number of years between the date of birth and the current date. If it's negative (which means that the person hasn't been born yet) or more than 100 years (we'll assume that people over 100 will be treated as being 100 years old), it rejects the value. Otherwise, it sets the value of the m_BDate local variable and calculates the value of the m_Age local variable.

You can implement write-only properties with the WriteOnly keyword and a Set section only, but write-only properties are rarely used — in my experience, only for storing passwords.

Calculating Property Values on the Fly

There's still a serious flaw in the implementation of the Age property. Can you see it? The person's age is up-to-date the moment the birth date is entered, but what if we read it back from a file or database three years later? It will still return the original value, which will no longer be the correct age. The Age property's value shouldn't be stored anywhere; it should be calculated from the person's birth date as needed. If we avoid storing the age to a local variable and calculate it on the fly, users will always see the correct age. Revise the Age property code to match Listing 8.8 and the property will now calculate the difference between the date of birth and the current date and return the correct person's age every time it's called.

Example 8.8. A calculated property

ReadOnly Property Age() As Integer
   Get
      Age = Convert.ToInt32(DateDiff(DateInterval.Year, m_BDate , Now()))
   End Get
End Property

Notice also that you no longer need the m_Age local variable because the age is calculated on the fly when requested, so remove its declaration from the class and remove the statement that sets the value of the m_Age variable in the BDate property's setter. As you can see, you don't always have to store property values to local variables. A property that returns the number of files in a directory, for example, also doesn't store its value in a local variable. It retrieves the requested information on the fly and furnishes it to the calling application. By the way, the calculations might still return a negative value if the user has changed the system's date, but this is a rather far-fetched scenario.

Your Minimal class is no longer so minimal. It exposes some functionality, and you can easily add more. Add properties for name, profession, and income, and add methods to calculate insurance rates based on a person's age and anything you can think of. Experiment with a few custom members, add the necessary validation code in your property procedures, and you'll soon find out that building and reusing custom classes is a simple and straightforward process. Of course, there's a lot more to learn about classes, but you already have a good understanding of the way classes combine code with data. Before continuing, let me introduce auto-implemented properties, which are a major convenience when you implement custom classes.

Auto-Implemented Properties

Quite often, actually more often than not, properties are implemented with straightforward code, without any validation code — just straight setters and getters like the following:

Private m_Occupation As String
Property Occupation() As String
   Get
      Return (m_Occupation)
   End Get
   Set (value As String)
      m_Occupation = value
End Property

The definition of many properties is based on a standard template, or boilerplate, which is always the same, except for the names of the properties and the corresponding local variables. If you don't need any additional code, shouldn't there be a simple method of defining a property? Indeed, with VB 2010 you can supply the name of the property and its type in a single statement like the following:

Public Property Occupation As String

And that's all it takes to create a straightforward property. Properties declared this way are known as auto-implemented properties, and they can simplify the coding of large classes considerably because most properties are usually implemented with the default setter and getter. Behind the scenes, the VB compiler generates the appropriate Get and Set segments of the property for you. As for the matching local variable, the compiler uses the same name as the property prefixed with an underscore. You can access this local variable from within the code that implements other properties, as in the following example:

Public Class Contact
    Public Property Name As String
    Public Property Company As String
    Public Property Occupation As String
    Private _Title
    Public Property Title() As String
        Get
            If _Occupation.Trim.Length > 0 Then
                Return _Occupation & "/" & _Title
            Else
                Return _Title
            End If
        End Get
        Set(ByVal value As String)
            _Title = value
        End Set
    End Property
End Class

The Contact class exposes three auto-implemented properties, the Name, Company, and Occupation properties and a fully implemented property, the Title property. Note that the Title property's Get segment takes into consideration the _Occupation local variable that holds the value of the Occupation auto-implemented property. Although the _Occupation variable is not declared anywhere in the class, it's being used in the Title property's implementation.

Auto-implemented properties are just a shorthand notation for properties, and they're converted into actual code by the compiler on the fly. Actually, when you type the statement

Public Property Company As String

the editor won't insert the stubs for the Get and Set segments; you must type the Get keyword on the following line and then press Enter for the editor to emit the stubs for the two segments of the property procedure.

Customizing Default Members

As you recall, when you created the Minimal class for the first time, before adding any code, the class exposed a few members — the default members, such as the ToString method (which returns the name of the class) and the Equals method (which compares two objects for reference equality). You can (and should) provide your custom implementation for these members; this is what I'm going to do in the following sections.

Customizing the ToString Method

The custom ToString method is implemented as a public function, and it must override the default implementation. The implementation of a custom ToString method is shown next:

Public Overrides Function ToString() As String
   Return "The infamous Minimal class"
End Function

As soon as you enter the keyword Overrides, the editor will suggest the names of the three members you can override: ToString, Equals, and GetHashCode. Select the ToString method, and the editor will insert a default implementation for you. The default implementation returns the string MyBase.ToString. Just replace the statement inserted by the editor with the one shown in the preceding code segment.

The Overrides keyword tells the compiler that this implementation overwrites the default implementation of the class. The original method's code isn't exposed, and you can't revise it. The Overrides keyword tells the compiler to "hide" the original implementation and use your custom ToString method instead. After you override a method in a class, the application using the class can no longer access the original method. Ours is a simple method, but you can return any string you can build in the function. For example, you can incorporate the value of the BDate property in the string:

Return("MINIMAL: " & m_BDate.ToShortDateString)

The value of the local variable m_BDate is the value of the BDate property of the current instance of the class. Change the BDate property, and the ToString method will return a different string.

Customizing the Equals Method

The Equals method exposed by most of the built-in objects can compare values, not references. Two Rectangle objects, for example, are equal if their dimensions and origins are the same. The following two rectangles are equal:

Dim R1 As New Rectangle(0, 0, 30, 60)
Dim R2 As New Rectangle
R2.X = 0
R2.Y = 0
R2.Width = 30
R2.Height = 60
If R1.Equals(R2) Then
   MsgBox("The two rectangles are equal")
End If

If you execute these statements, a message box confirming the equality of the two objects will pop up. The two variables point to different objects (that is, different instances of the same class), but the two objects are equal because they have the same origin and same dimensions. The Rectangle class provides its own Equals method, which knows how to compare two Rectangle objects. If your class doesn't provide a custom Equals method, all the compiler can do is compare the objects referenced by the two variables. In the case of our Minimal class, the Equals method returns True if the two variables point to the same object (which is the same instance of the class). If the two variables point to two different objects, the default Equals method will return False, even if the two objects are the same.

You're probably wondering what makes two objects equal. Is it all their properties or perhaps some of them? Two objects are equal if the Equals method says so. You should compare the objects in a way that makes sense, but you're in no way limited as to how you do this. In a very specific application, you might decide that two rectangles are equal because they have the same area, or perimeter, regardless of their dimensions and origin, and override the Rectangle object's Equals method. In the Minimal class, for example, you might decide to compare the birth dates and return True if they're equal. Listing 8.9 is the implementation of a possible custom Equals method for the Minimal class.

Example 8.9. A custom Equals method

Public Overrides Function Equals(ByVal obj As Object) As Boolean
   Dim O As Minimal = CType(obj, Minimal)
   If O.BDate = m_BDate Then
      Equals = True
   Else
      Equals = False
   End If
End Function

Know What You Are Comparing

The Equals method shown in Listing 8.9 assumes that the object you're trying to compare to the current instance of the class is of the same type. Because you can't rely on developers to catch all their mistakes, you should know what you're comparing before you attempt to perform the comparison. A more-robust implementation of the Equals method is shown in Listing 8.10. This implementation tries to convert the argument of the Equals method to an object of the Minimal type and then compares it to the current instance of the Minimal class. If the conversion fails, an InvalidCastException is thrown and no comparison is performed.

Example 8.10. A more-robust Equals method

Public Overrides Function Equals(ByVal obj As Object) As Boolean
   Dim O As New Minimal()
   Try
       O = DirectCast(obj, Minimal)
   Catch typeExc As InvalidCastException
      Throw typeExc
      Exit Function
   End Try
   If O.BDate = m_BDate Then
      Equals = True
   Else
      Equals = False
   End If
End Function

The Is Operator

The equals (=) operator can be used in comparing all built-in objects. The following statement is quite valid, as long as the R1 and R2 variables were declared of the Rectangle type:

If R1 = R2 Then
    MsgBox("The two rectangles are equal")
End If

This operator, however, can't be used with the Minimal custom class. Later in this chapter, you'll learn how to customize operators in your class. In the meantime, you can use only the Is operator, which compares for reference equality (whether the two variables reference the same object), and the Equals method. If the two variables R1 and R2 point to the same object, the following statement will return True:

If R1 Is R2 Then
    MsgBox("The two variables reference the same object")
End If

The Is operator tells you that the two variables point to a single object. There's no comparison here; the compiler simply figures out whether they point to same object in memory. It will return True if a statement such as the following has been executed before the comparison:

obj2 = obj1

If the Is operator returns True, there's only one object in memory and you can set its properties through either variable.

Custom Enumerations

Let's add a little more complexity to our class. Because we're storing birth dates to our custom objects, we can classify persons according to their age. Most BASIC developers will see an opportunity to use constants here. Instead of using constants to describe the various age groups, we'll use an enumeration with the following group names:

Public Enum AgeGroup
   Infant
   Child
   Teenager
   Adult
   Senior
   Overaged
End Enum

These statements must appear outside any procedure in the class, and we usually place them at the beginning of the file, right after the declaration of the class. Public is an access modifier (we want to be able to access this enumeration from within the application that uses the class). Enum is a keyword: It specifies the beginning of the declaration of an enumeration and it's followed by the enumeration's name. The enumeration itself is a list of integer values, each one mapped to a name. In our example, the name Infant corresponds to 0, the name Child corresponds to 1, and so on. The list of the enumeration's members ends with the End Enum keyword. You don't really care about the actual values of the names because the very reason for using enumerations is to replace numeric constants with more-meaningful names. You'll see shortly how enumerations are used both in the class and the calling application.

Now add to the class the GetAgeGroup method (Listing 8.11), which returns the name of the age group to which the person represented by an instance of the Minimal class belongs. The name of the group is a member of the AgeGroup enumeration.

Example 8.11. Using an enumeration

Public Function GetAgeGroup() As AgeGroup
   Select Case m_Age
      Case Is < 3 : Return (AgeGroup.Infant)
      Case Is < 10 : Return (AgeGroup.Child)
      Case Is < 21 : Return (AgeGroup.Teenager)
      Case Is < 65 : Return (AgeGroup.Adult)
Case Is < 100 : Return (AgeGroup.Senior)
      Case Else : Return (AgeGroup.Overaged)
   End Select
End Function

The GetAgeGroup method returns a value of the AgeGroup type and you can compare it to members of the same enumeration. Switch to the form's code window, add a new button, and enter the statements from Listing 8.12 in its event handler.

Example 8.12. Using the enumeration exposed by the class

Protected Sub Button1_Click(...)
                 Handles Button1.Click
   Dim obj As Minimal
        obj = New Minimal()
        Try
            obj.BDate = InputBox("Please Enter your birthdate")
        Catch ex As ArgumentException
            MsgBox(ex.Message)
            Exit Sub
        End Try
        Debug.WriteLine(obj.Age)
        Dim discount As Single
        Dim grp As Minimal.AgeGroup = obj.GetAgeGroup
        Select Case grp
           Case Minimal.AgeGroup.Infant, Minimal.AgeGroup.Child
              discount = 0.4
           Case Minimal.AgeGroup.Teenager
              discount = 0.5
           Case Minimal.AgeGroup.Senior
              discount = 0.2
           Case Else
       End Select

        MsgBox("Your age is " & obj.Age.ToString &
           " and you belong to the " &
           obj.GetAgeGroup.ToString &
           " group" & vbCrLf & "Your discount is " &
           Format(discount, "Percent"))
End Sub

This routine calculates discounts based on the person's age. Notice that we don't use numeric constants in our code, just descriptive names. Moreover, the possible values of the enumeration are displayed in a drop-down list by the IntelliSense feature of the IDE as needed (Figure 8.3), and you don't have to memorize them or look them up as you would with constants. I've used an implementation with multiple If statements in this example, but you can perform the same comparisons by using a Select Case statement.

The members of an enumeration are displayed automatically in the IDE as you type.

Figure 8.3. The members of an enumeration are displayed automatically in the IDE as you type.

You've seen the basics of working with custom classes in a VB application. Let's switch to a practical example that demonstrates not only the use of a real-world class, but also how classes can simplify the development of a project.

VB 2010 at Work: The Contacts Project

In Chapter 6, "Working with Forms," I briefly discussed the Contacts application. This application uses a custom structure to store the contacts and provides four navigational buttons to allow users to move to the first, last, previous, and next contact. Now that you have learned how to program the ListBox control and how to use custom classes in your code, you can revise the Contacts application. First, you'll implement a class to represent each contact. The fields of each contact (company and contact names, addresses, and so on) will be implemented as properties and they will be displayed in the TextBox controls on the form.

You'll also improve the user interface of the application. Instead of the rather simplistic navigational buttons, you'll place all the company names in a sorted ListBox control. The user can easily locate the desired company and select it from the list to view its fields. The editing buttons at the bottom of the form work as usual, but you no longer need the navigational buttons. Figure 8.4 shows the revised Contacts application.

The interface of the Contacts application based on the ListBox control

Figure 8.4. The interface of the Contacts application based on the ListBox control

Copy the contents of the Contacts folder you used when you worked on the Contacts project in Chapter 6 under a new folder and open the project by double-clicking the solution's name, Contacts.sln. You will also find the revised project in this chapter's projects. First, delete the declaration of the Contact structure and add a class to the project. Name the new class Contact and enter the code from Listing 8.13 into it. The names of the private members of the class are the same as the actual property names, and they begin with an underscore. (This is a good convention that lets you easily distinguish whether a variable is private and the property value it stores.) The implementation of the properties is trivial, so I'm not showing the code for all of them.

Example 8.13. The Contact class

<Serializable()> Public Class Contact
    Private _companyName As String
    Private _email As String

    Property CompanyName() As String
        Get
            CompanyName = _companyName
        End Get
        Set(ByVal value As String)
            If value Is Nothing Or value = "" Then
                Throw New Exception("Company Name field can't be empty")
                Exit Property
            End If
            _companyName = value
        End Set
    End Property

    Property ContactName As String

    Property Address1 As String

    Property Address2 As String

    Property City As String

    Property State As String

    Property ZIP As String

    Property tel As String

    Property EMail As String
    Get
            EMail = _email
        End Get
        Set(ByVal value As String)
If value.Contains("@") Or value.Trim.Length = 0 Then
                _email = Value
            Else
                Throw New Exception("Invalid e-mail address!")
            End If
        End Set
    End Property

    Property URL As String

    Overrides Function ToString() As String
        If _contactName = "" Then
            Return _companyName
        Else
            Return _companyName & vbTab & "(" & _contactName & ")"
        End If
    End Function

    Public Sub New()
        MyBase.New()
    End Sub

    Public Sub New(ByVal CompanyName As String,
                   ByVal LastName As String, ByVal FirstName As String)
        MyBase.New()
        Me.ContactName = LastName & ", " & FirstName
        Me.CompanyName = CompanyName
    End Sub

    Public Sub New(ByVal CompanyName As String)
        MyBase.New()
        Me.CompanyName = CompanyName
    End Sub
End Class

The first thing you'll notice is that the class's definition is prefixed by the <Serializable()> keyword. The topic of serialization is discussed in Chapter 13, "XML in Modern Programming," but for now all you need to know is that the .NET Framework can convert objects to a text or binary format and then store them in files. Surprisingly, this process is quite simple; as you will see, we'll be able to dump an entire collection of Contact objects to a file with a single statement. The <Serializable()> keyword is an attribute of the class, and (as you will see later in this book) there are more attributes you can use with your classes — or even with your methods. The most prominent method attribute is the <WebMethod> attribute, which turns a regular function into a web method.

The various fields of the Contact structure are now properties of the Contact class. The implementation of the properties is trivial except for the CompanyName and EMail properties, which contain some validation code. The Contact class requires that the CompanyName property have a value; if it doesn't, the class throws an exception. Likewise, the EMail property must contain the symbol @. Finally, the class provides its own ToString method, which returns the name of the company followed by the contact name in parentheses. All other properties are auto-implemented.

The ListBox control, in which we'll store all contacts, displays the value returned by the object's ToString method, which is why you have to provide your own implementation of this method to describe each contact. The company name should be adequate, but if there are two companies by the same name, you can use another field to differentiate them. I used the contact name, but you can use any of the other properties (the URL would be a good choice).

Although the ListBox displays a string, it stores the object itself. In essence, it's used not only as a navigational tool, but also as a storage mechanism for our contacts. Now, we must change the code of the main form a little. Start by removing the navigational buttons; we no longer need them. Their function will be replaced by a few lines of code in the ListBox control's SelectedIndexChanged event. Every time the user selects another item on the list, the statements shown in Listing 8.14 display the contact's properties in the various TextBox controls on the form. The currentContact variable is an integer that represents the index of the currently selected item, and it must be declared outside the subroutine because it must be accessed by other event handlers as well.

Example 8.14. Displaying the fields of the selected Contact object

Private Sub ListBox1_SelectedIndexChanged(...)
              Handles ListBox1.SelectedIndexChanged
   currentContact = ListBox1.SelectedIndex
   ShowContact()
End Sub

The ShowContact() subroutine reads the object stored at the location specified by the currentContact variable and displays its properties in the various TextBox controls on the form. The TextBox controls are normally read-only, except when users are editing a contact. This action is signaled to the application when the user clicks the Edit or the Add button on the form.

When a new contact is added, the code reads its fields from the controls on the form, creates a new Contact object, and adds it to the ListBox control. When a contact is edited, a new Contact object replaces the currently selected object on the control. The code is similar to the code of the Contacts application. I should mention that the ListBox control is locked while a contact is being added or edited because it doesn't make sense to select another contact at that time.

Adding, Editing, and Deleting Contacts

To delete a contact (Listing 8.15), we simply remove the currently selected object from the ListBox control. In addition, we must select the next contact on the list (or the first contact if the deleted one was last in the list). The code of this event handler makes use of the currentContact variable to remove the currently selected item from the list.

Example 8.15. Deleting an object from the ListBox

Private Sub bttnDelete_Click(...) Handles bttnDelete.Click
   If currentContact > −1 Then
      ListBox1.Items.RemoveAt(currentContact)
      currentContact = ListBox1.Items.Count - 1
      If currentContact = −1 Then
         ClearFields()
         MsgBox("There are no more contacts")
      Else
         ShowContact()
      End If
   Else
        MsgBox("No current contacts to delete")
     End If
End Sub

When you add a new contact, the following code is executed in the Add button's Click event handler:

Private Sub bttnAdd_Click(...) Handles bttnAdd.Click
  adding = True
  ClearFields()
  HideButtons()
  ListBox1.Enabled = False
End Sub

The controls are cleared in anticipation of the new contact's fields, and the adding variable is set to True. The OK button is clicked to end either the addition of a new record or an edit operation. The code behind the OK button is shown in Listing 8.16.

Example 8.16. Committing a new or edited record

Private Sub bttnOK_Click(...) Handles bttnOK.Click
    If SaveContact() Then
        ListBox1.Enabled = True
        ShowButtons()
    End If
End Sub

As you can see, the same subroutine handles both the insertion of a new record and the editing of an existing one. All the work is done by the SaveContact() subroutine, which is shown in Listing 8.17.

Example 8.17. The SaveContact() subroutine

Private Function SaveContact() As Boolean
    Dim contact As New Contact
    Try
        contact.CompanyName = txtCompany.Text
        contact.ContactName = txtContact.Text
        contact.Address1 = txtAddress1.Text
        contact.Address2 = txtAddress2.Text
        contact.City = txtCity.Text
        contact.State = txtState.Text
        contact.ZIP = txtZIP.Text
        contact.tel = txtTel.Text
        contact.EMail = txtEMail.Text
        contact.URL = txtURL.Text
    Catch ex As Exception
        MsgBox(ex.Message)
        Return False
    End Try
    If adding Then
        ListBox1.Items.Add(contact)
    Else
        ListBox1.Items(currentContact) = contact
    End If
    Return True
End Function

The SaveContact() function uses the adding variable to distinguish between an add and an edit operation and either adds the new record to the ListBox control or replaces the current item in the ListBox with the values on the various controls. Because the ListBox is sorted, new contacts are automatically inserted in the correct order. If an error occurs during the operation, the SaveContact() function returns False to alert the calling code that the operation failed (most likely because one of the assignment operations caused a validation error in the class's code). Note that the SaveContact routine uses the currentContact variable to determine the selected item on the list.

The last operation of the application is the serialization and deserialization of the items in the ListBox control. Serialization is the process of converting an object to a stream of bytes for storing to a disk file, and deserialization is the opposite process. To serialize objects, we first store them into an ArrayList object, which is a dynamic array that stores objects and can be serialized as a whole. Likewise, the disk file is deserialized into an ArrayList to reload the persisted data back to the application; then each element of the ArrayList is moved to the Items collection of the ListBox control. ArrayLists and other Framework collections are discussed in Chapter 12, "Storing Data in Collections," and object serialization is discussed in Chapter 13. You can use these features to test the application and examine the corresponding code after you read about ArrayLists and serialization.

Object Constructors

Let's switch to a few interesting topics in programming with objects. Objects are instances of classes, and classes are instantiated with the New keyword. The New keyword can be used with a number of arguments, which are the initial values of some of the object's basic properties. To construct a rectangle, for example, you can use either of two statements. You can use this one:

Dim shape1 As Rectangle = New Rectangle()
shape1.Width = 100
shape1.Height = 30

Or you can use the following one:

Dim shape1 As Rectangle = New Rectangle(100, 30)

The objects in the Minimal class can't be initialized to specific values of their properties, and they expose the simple form of the New constructor — the so-called parameterless constructor. Every class has a parameterless constructor, even if you don't specify it. You can implement parameterized constructors, which allow you to pass arguments to an object as you declare it. These arguments are usually the values of the object's basic properties. Parameterized constructors don't pass arguments for all the properties of the object; they expect only enough parameter values to make the object usable.

Parameterized constructors are implemented via public subroutines that have the name New(). You can have as many overloaded forms of the New() subroutine as needed. Most of the built-in classes provide a parameterless constructor, but the purists of OOP will argue against parameterless constructors. Their argument is that you shouldn't allow users of your class to create invalid instances of it. A class for describing customers, for example, should expose at least a Name property. A class for describing books should expose a Title and an ISBN property. If the corresponding constructor requires that these properties be specified before you create an instance of the class, you'll never create objects with invalid data. There are cases, however, where this isn't possible. When you call a function that returns a custom object, for example, you must declare a variable of the same type and assign the function to this variable:

Dim C As Contact
C = ListBox1.SelectedItem

Here we can't create a new Contact object because we want to store in the C variable an existing object. Is there a good reason to create an object variable before you have an object to store in it?

Let's add a parameterized constructor to our Contact class. Each contact should have at least a name; here's a parameterized constructor for the Contact class:

Public Sub New(ByVal CompanyName As String)
    MyBase.New()
    Me.CompanyName = CompanyName
End Sub

The code is trivial, with the exception of the statement that calls the MyBase.New() subroutine. MyBase is an object that lets you access the members of the base class (a topic that's discussed in detail later in this chapter). The reason you must call the New method of the base class is that the base class might have its own constructor, which can't be called directly. You must always insert this statement in your constructors to make sure any initialization tasks that must be performed by the base class will not be skipped.

The Contact class's constructor accepts a single argument: the company name (this property can't be a blank string). Another useful constructor for the same class accepts two additional arguments, the contact's first and last names, as follows:

Public Sub New(ByVal CompanyName As String,
               ByVal LastName As String, ByVal FirstName As String)
    MyBase.New()
    Me.ContactName = LastName & ", " & FirstName
    Me.CompanyName = CompanyName
End Sub

With the two parameterized constructors in place, you can create new instances of the Contact class by using a statement such as the following:

Dim contact1 As New Contact("Around the Horn")

Or you can use a statement such as this:

Dim contact1 As New Contact("Around the Horn", "Hardy", "Thomas")

Notice the lack of the Overloads (or Overrides) keyword. Constructors can have multiple forms and don't require the use of Overloads — just supply as many implementations of the New() subroutine as you need.

One last but very convenient technique to initialize objects allows you to supply values for as many properties of the new object as you wish, using the With keyword. The With keyword is followed by the names of the properties you want to initialize and their values in a comma-separated list, as shown in the following statements, which create two new instances of the Person class, and they initialize each one differently:

Dim P1 As New Person With
       {.LastName = "Doe", .FirstName = "Joe"})
Dim P2 As New Person With
       {.LastName = "Doe", .Email = "[email protected]"})

Note that the property names are prefixed with the period. The With statement is a shorthand notation for accessing multiple members of an instance of a class without having to repeat the class's name. This syntax allows you to quickly initialize new objects regardless of their constructors; in effect, you can create your own constructor for any class. This technique will be handy when combining object initialization with other statements, such as in the following example, which adds a new object to a list:

Persons.Add(New Person With {.LastName = "Doe", .FirstName = "Joe"})

While the New function is the class constructor, the syntax with the With keyword is not a constructor, although it creates a new instance of the class and initializes its properties. The process of initializing an instance of a class is known as object initialization. An advantage of the With keyword is that as soon as you type the opening bracket, the editor will display the list of properties you can initialize in the IntelliSense box.

Using the SimpleClass in Other Projects

The projects we built in this section are Windows applications that contain a Class module. The class is contained within the project, and it's used by the project's main form. What if you want to use this class in another project?

First, you must change the type of the project. A Windows project can't be used as a component in another project. Right-click the SimpleClass project and choose Properties. In the project's Property Pages dialog box, switch to the Application tab, locate the Application Type drop-down list, and change the project's type from Windows Forms Application to Class Library, as shown in Figure 8.5. Then close the dialog box. When you return to the project, right-click the TestForm and select Exclude From Project. A class doesn't have a visible interface, and there's no reason to include the test form in your project.

Setting a project's properties through the Property Pages dialog box

Figure 8.5. Setting a project's properties through the Property Pages dialog box

From the main menu, choose Build

Setting a project's properties through the Property Pages dialog box

Let's use the SimpleClass.dll file in another project. Start a new Windows application, open the Project menu, and add a reference to the SimpleClass. Choose Project

Setting a project's properties through the Property Pages dialog box

The compiler will place the DLL in the folder objx86Debug under the project's folder. The SimpleClass component will be added to the project. You can now declare a variable of the SimpleClass.Minimal type and call its properties and methods:

Dim obj As New SimpleClass.Minimal
obj.BDate = #10/15/1992#
obj.strProperty = 5544
MsgBox(obj.NegateNumber())

If you want to keep testing the SimpleClass project, add the TestForm to the original project (right-click the project's name, choose Add

Setting a project's properties through the Property Pages dialog box

Adding references to multiple DLLs scattered through your disk or local network can become a nightmare, especially if multiple developers are working on the same projects. All related items should belong to the same solution, so don't be afraid to add multiple classes to the same project. Actually, this is the recommended practice and it will simplify the task of referencing the classes from within the other projects.

Adding a reference to an existing class to a new project

Figure 8.6. Adding a reference to an existing class to a new project

Firing Events

In addition to methods and properties, classes can also fire events. You will find that events are not quite as common with classes. Controls have many events because they expose a visible interface and the user interacts through this interface (clicks, drags and drops, and so on). But classes can also raise events. Class events can come from three different sources:

Progress events

A class might raise an event to indicate the progress of a lengthy process or indicate that an internal variable or property has changed value. The PercentDone event is a typical example. A process that takes a while to complete reports its progress to the calling application with this event, which is fired periodically. These events, which are called progress events, are the most common type of class events.

Time events

Time events are based on a timer. They're not very common, but you can implement alarms, job schedulers, and similar applications. You can set an alarm for a specific time or an alarm that will go off after a specified interval.

External events

External events, such as the completion of an asynchronous operation, can also fire events. For example, a class might initiate a file download and notify the application when the file arrives.

To fire an event from within a class, you must do the following:

  1. First you must declare the event and its signature in your class. The declaration must appear in the class, not in any procedure. A simple event, with no arguments, should be declared as follows (ShiftEnd is the name of the event — an event that signals the process):

    Public Event ProcessCompleted()
  2. Fire the event from within your class code with the RaiseEvent method:

    RaiseEvent ProcessCompleted()

    That's all as far as the class is concerned.

  3. The application that uses the custom class must declare it with the WithEvents keyword. Otherwise, it will still be able to use the class's methods and properties, but the events raised by the class will go unnoticed. The following statement creates an instance of the class and listens for any event:

    Dim WithEvents obj As New Minimal
  4. Finally, the calling application must provide a handler for the specific event. Because the class was declared with the WithEvents keyword, its name will appear in the list of objects in the editor window and its ProcessCompleted event will appear in the list of events (Figure 8.7). Insert the code you want to handle this event in the procedure obj.ProcessCompleted.

Events usually pass information to the calling application. In VB, all events pass two arguments to the application: a reference to the object that fired the event and another argument, which is an object that contains information specific to the event.

The arguments of an event are declared just as the arguments of a procedure are declared. The following statement declares an event that's fired every few seconds to report the progress of a process as a percent complete value. The event passes a single parameter value to the application that intercepts it:

Programming a custom class's event

Figure 8.7. Programming a custom class's event

Public Event ProcessProgress(ByVal percent As Integer)

The progress is reported as an integer between 0 and 100. To raise this event from within a class code, call the RaiseEvent statement as before, passing a value of the appropriate type, as shown next, where the iProgress variable is maintained by the class code and is increased with every iteration:

RaiseEvent PercentProgress(iProgress)

That's all it takes to fire an event from within your custom class. In Chapter 9, "Building Custom Windows Controls," you will find several examples of custom events.

Some events expose a Cancel argument, which you can set to True to cancel an operation that takes place in the background. For example, users should be able to abort the process of counting the files in a volume, which can take minutes depending on the size of the volume. In the SimpleClass project (available for download from www.sybex.com/go/masteringvb2010), you will find the Form2. Form2 implements a method for counting files (the ProcessFiles method). This method raises the PercentProgress event every time it starts processing a different folder. The PercentProgress event exposes the Cancel argument, which the calling application can set to True to end the operation. Make Form2 the startup of the project to experiment with a class that interacts with the calling application with events. The process takes a while, but you can interrupt it by pressing the Escape key. The code is adequately documented and will help you understand how to retrieve multiple parameters through an event handler as well as how to pass information back to the class through its events.

Instance and Shared Methods

As you have seen in earlier chapters, some classes allow you to call some of their members without first creating an instance of the class. The DateTime class, for example, exposes the IsLeapYear method, which accepts as an argument a numeric value and returns a True/False value that indicates whether the year is a leap year. You can call this method through the DateTime (or Date) class without having to create a variable of the DateTime type, as shown in the following statement:

If DateTime.IsLeapYear(1999) Then
   { process a leap year}
End If

A typical example of classes that can be used without explicit instances is the Math class. To calculate the logarithm of a number, you can use an expression such as this one:

Math.Log(3.333)

The properties and methods that don't require you to create an instance of the class before you call them are called shared methods. Methods that must be applied to an instance of the class are called instance methods. By default, all methods are instance methods. To create a shared method, you must prefix the corresponding function declaration with the Shared keyword, just as you would a shared property.

Why do we need shared methods, and when should we create them? If a method doesn't apply to a specific instance of a class, make it shared. In other words, if a method doesn't act on the properties of the current instance of the class, it should be shared. Let's consider the DateTime class. The DaysInMonth method returns the number of days in the month (of a specific year) that is passed to the method as an argument. You don't really need to create an instance of a Date object to find out the number of days in a specific month of a specific year, so the DaysInMonth method is a shared method and can be called as follows:

DateTime.DaysInMonth(2010, 2)

Think of the DaysInMonth method this way: Do I need to create a new date to find out if a specific month has 30 or 31 days? If the answer is no, then the method is a candidate for a shared implementation.

The AddDays method, on the other hand, is an instance method. We have a date to which we want to add a number of days and construct a new date. In this case, it makes sense to apply the method to an instance of the class — the instance that represents the date to which we add the number of days — rather than passing the date as argument to the AddDays method.

If you take a moment to reflect on shared and instance members, you'll come to the conclusion that all members could have been implemented as shared members and accept the data they act upon as arguments. This approach, however, would reduce classes to collections of functions, just like the built-in functions of Visual Basic. The idea behind classes, however, is to combine data with code. If you implement a class with shared members, you lose one of the major advantages of OOP. One of the reasons for turning to object-oriented programming was that languages were being enhanced with every new version to a point that they were bloated and developers couldn't memorize all the functions.

The SharedMembers sample project (available for download from www.sybex.com/go/masteringvb2010) is a simple class that demonstrates the differences between a shared and an instance method. Both methods do the same thing: They reverse the characters in a string. The IReverseString method is an instance method; it reverses the current instance of the class, which is a string. The SReverseString method is a shared method; it reverses its argument. Listing 8.18 shows the code that implements the SharedMembersClass component.

Example 8.18. A class with a shared and an instance method

Public Class SharedMembersClass
   Private _strProperty As String

   Sub New(ByVal str As String)
      _strProperty = str
   End Sub

   Sub New()
End Sub

    Public Property strProperty As String
    Get
       Return(_strProperty)
    End Get
    Set (ByVal value As String)
       _strProperty = value
    End Set
End Property
     Public Function IReverseString() As String
        Return (StrReverse(strProperty))
     End Function

     Public Shared Function SReverseString(ByVal str As String) As String
        Return (StrReverse(str))
     End Function
  End Class

Note that I had to declare the parameterless constructor in the class. This form of the constructor should be included only if you add multiple forms of the constructor. If you don't care about constructors that accept parameters, you don't have to list the default constructor explicitly. The instance method acts on the current instance of the class. This means that the class must be initialized to a string, and this is why the New constructor requires a string argument. To test the class, add a form to the project, make it the Startup object, and add two buttons to it. The code behind the two buttons is shown next:

Private Sub Button1_Click(...) Handles Button1.Click
   Dim testString As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
   Dim obj As New SharedMembersClass(testString)
   Debug.WriteLine(obj.IReverseString)
End Sub

Private Sub Button2_Click(...) Handles Button2.Click
   Dim testString As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
   Debug.WriteLine(SharedMembersClass.SReverseString(testString))
End Sub

The code behind the first button creates a new instance of the SharedMembersClass and calls its IReverseString method. The second button calls the SReverseString method through the class's name and passes the string to be reversed as an argument to the method.

A class can also expose shared properties. There are situations in which you want all instances of a class to see the same property value. Let's say you want to keep track of the users currently accessing your class. You can declare a method that must be called to enable the class, and this method signals that another user has requested your class. This method could establish a connection to a database or open a file. We'll call it the Connect method. Every time an application calls the Connect method, you can increase an internal variable by one. Likewise, every time an application calls the Disconnect method, the same internal variable is decreased by one. This internal variable can't be private because it will be initialized to zero with each new instance of the class. You need a variable that is common to all instances of the class. Such a variable should be declared with the Shared keyword.

Let's add a shared variable to our Minimal class. We'll call it LoggedUsers, and it will be read-only. Its value is reported via the Users property, and only the Connect and Disconnect methods can change its value. Listing 8.19 is the code you must add to the Minimal class to implement a shared property.

Example 8.19. Implementing a shared property

Shared LoggedUsers As Integer
ReadOnly Property Users() As Integer
   Get
      Users = LoggedUsers
   End Get
End Property

Public Function Connect() As Integer
   LoggedUsers = LoggedUsers + 1
   ' { your own code here}
End Function

Public Function Disconnect() As Integer
   If LoggedUsers > 1 Then
      LoggedUsers = LoggedUsers - 1
   End If
   ' { your own code here}
End Function

To test the shared variable, add a new button to the form and enter the code in Listing 8.20 in its Click event handler. (The lines with the bold numbers are the values reported by the class; they're not part of the listing.)

Example 8.20. Testing the LoggedUsers shared property

Private Sub Button3_Click(ByVal sender As Object,
                 ByVal e As System.EventArgs)
   Dim obj1 As New SharedMembersClass
   obj1.Connect()
   Debug.WriteLine(obj1.Users)
1
   obj1.Connect()
   Debug.WriteLine(obj1.Users)
2
   Dim obj2 As New SharedMembersClass
obj2.Connect()
   Debug.WriteLine(obj1.Users)
3
   Debug.WriteLine(obj2.Users)
3
   Obj2.Disconnect()
   Debug.WriteLine(obj2.Users)
2
End Sub

If you run the application, you'll see the values displayed under each Debug.WriteLine statement in the Output window. As you can see, both the obj1 and obj2 variables access the same value of the Users property. Shared variables are commonly used in classes that run on a server and service multiple applications. In effect, they're the class's global variables, which can be shared among all the instances of a class. You can use shared variables to keep track of connection time, the total number of rows accessed by all users of the class in a database, and other similar quantities.

A "Real" Class

The StringTools project, which is included with this chapter's projects, contains the StringTools class, which exposes a few interesting methods. The first two methods are the ExtractPathName and ExtractFileName methods, which extract the filename and pathname from a full filename. If the full name of a file is C:DocumentsRecipesChineseWon Ton.txt, the ExtractPathName method will return the substring C:DocumentsRecipesChinese, and the ExtractFileName method will return the substring Won Ton.txt.

A third method, called Num2String, converts numeric values (amounts) to the equivalent strings. For example, it can convert the amount $12,544 to the string Twelve Thousand, Five Hundred And Forty Four dollars. No other class in the Framework provides this functionality, and any program that prints checks can use this class.

The Num2String method uses three properties of the StringTools class: the Case, Delimiter, and Padding properties. The Case property determines the case of the characters in the string returned by the method. The Delimiter property specifies the special characters that should appear before and after the string. Finally, the Padding property specifies the character that will appear between groups of digits. The values each of these properties can take on are members of the appropriate enumeration:

PaddingEnum

DelimiterEnum

CaseEnum

paddingCommas

delimiterNone

caseCaps

paddingSpaces

delimiterAsterisk

caseLower

paddingDashes

delimiter3Asterisks

caseUpper

The values for each of these properties are implemented as enumerations, and you need not memorize their names. As you enter the name of the property followed by the equal sign, the appropriate list of values will pop up and you can select the desired member. Listing 8.21 presents the UseCaseEnum enumeration and the implementation of the UseCase property.

Example 8.21. The CaseEnum enumeration and the UseCase property

Enum CaseEnum
   caseCaps
   caseLower
   caseUpper
End Enum

Private varUseCase As CaseEnum
Public Property [Case]() As CaseEnum
   Get
        Return (varUseCase)
   End Get
   Set
      varUseCase = Value
   End Set
End Property

Notice that the name of the Case property is enclosed in square brackets. This is necessary when you're using a reserved keyword as a variable, property, method, or enumeration member name. Alternatively, you can use a different name for the property to avoid the conflict altogether.

To test the StringTools class, create a test form like the one shown in Figure 8.8. Then enter the code from Listing 8.22 in the Click event handler of the two buttons.

Example 8.22. Testing the StringTools class

Protected Sub Button1_Click(...) Handles Button1.Click
   TextBox1.Text = Convert.ToDecimal(
                    TextBox1.Text).ToString("#,###.00")
     Dim objStrTools As New StringTools()
     objStrTools.Case = StringTools.CaseEnum.CaseCaps
     objStrTools.Delimiter = StringTools.DelimitEnum.DelimiterNone
     objStrTools.Padding = StringTools.PaddingEnum.PaddingCommas
     TextBox2.Text = objStrTools.Num2String(Convert.ToDecimal(TextBox1.Text))
End Sub

Protected Sub Button2_Click(...) Handles Button2.Click
   Dim objStrTools As New StringTools()
   openFileDialog1.ShowDialog()
   Dim fName as String
   fName = OpenFileDialog1.FileName
   Debug.writeline(objStrTools.ExtractPathName(fName))
   Debug.WriteLine(objStrTools.ExtractFileName(fName))
End Sub
The test form for the StringTools class

Figure 8.8. The test form for the StringTools class

Nesting Classes

In the examples so far, we used simple classes with properties that are basic data types. Practical classes use nested classes and their properties are not basic data types. The very essence of a class is to encapsulate the complexity of an entity and present a simplified view of that particular entity. Let's consider a simple, yet quite practical example.

The Stock class example is fairly simple, but it demonstrates the use of nested classes. Typically, an application uses high-level classes, such as Customer, Invoice, Product, and so on. These classes are usually too complicated to be built with the basic data types, so we nest the classes in many levels, creating a hierarchy of classes that reflects the hierarchy of the entities we're modeling. Consider the Address property of the Customer class. Doesn't it make sense to create an Address class with the details of the address and reuse it in the Customer class? We could also use it with the Supplier class and possibly other entities that have addresses. An even better example is that of an invoice. The invoice's body contains the items sold: product codes and names, prices, units, and so on.

Let's create a Detail class that represents the details of an invoice:

Public Class Detail
   Public Property ProductID As String
   Public Property ProductName As String
   Public Property UnitPrice As Decimal
   Public Property Units As Integer
End Class

Then, we can create a class that represents the entire invoice and make use of the Detail class:

Public Class Invoice
    Public InvoiceNumber As String
    Public CustomerID As Long
    Public InvoiceDate As DateTime
    Public Details() As Detail

    Public Class Detail
        Public Property ProductID As String
        Public Property ProductName As String
        Public Property UnitPrice As Decimal
        Public Property Units As Integer
    End Class
End Class

Since we're on the topic of nesting classes, you should try to combine the ProductID and ProductName properties into a new class, the Product class, and use this class in the definition of the Detail class.

One more interesting topic I should mention briefly here is how to initialize objects with nontrivial members. Let's consider an Invoice class, which exposes a member that is an array of custom objects. Could you initialize an instance of the Invoice class in a single statement? You may wish to give this a try before you look at the code.

The statement is a bit complicated, so let me introduce it gradually. To create an instance of the Invoice class, you'd use a statement like the following:

Dim detail = New Invoice.Detail With {.ProductID = 11, .ProductName = "item1",
                         .UnitPrice = 9.95, .Units = 3}

To populate the Invoice.Detail property, you must create an array with as many elements as there are detail lines in the invoice. The array's type should be of the Invoice.Detail type, as in the following example:

Dim details(1) As Invoice.Detail
Details(0) = New Invoice.Detail With {.ProductID = 101, .ProductName = "item1",
                         .UnitPrice = 9.95, .Units = 3}
Details(1) = New Invoice.Detail With {.ProductID = 102, .ProductName = "item2",
                         .UnitPrice = 4.45, .Units = 12}

Finally, you can combine all initialization statements into a single statement that initialized an invoice object:

Dim inv As New Invoice With
        {.CustomerID = 1001, .InvoiceDate = Now,
         .InvoiceNumber = "101-1",
         .Details = {New Invoice.Detail With
                         {.ProductID = 101, .ProductName = "item1",
                          .UnitPrice = 9.95, .Units = 3},
                          New Invoice.Det0ail With
                              {.ProductID = 102, .ProductName = "item2",
                               .UnitPrice = 4.45, .Units = 12}}}

The code is probably simpler to write than it is to read. Start with simple elements, like integers, that will help you get the brackets right, and then replace each simple value with the appropriate object constructor. You can also count on IntelliSense, which will show you the members available at each stage every time you type the period in a With clause.

Operator Overloading

In this section you'll learn about an interesting (but quite optional) feature of class design: how to customize the usual operators. Some operators in Visual Basic act differently on various types of data. The addition operator (+) is the most typical example. When used with numbers, the addition operator adds them. When used with strings, however, it concatenates the strings. The same operator can perform even more complicated calculations with the more-elaborate data types. When you add two variables of the TimeSpan type, the addition operator adds their durations and returns a new TimeSpan object. Each instance of the TimeSpan class is initialized with three integer values, which are the number of hours, minutes, and seconds in the time interval. If you execute the following statements, the value 3882 will be printed in the Output window (this value is the number of seconds in a time span of 1 hour, 4 minutes, and 42 seconds):

Dim TS1 As New TimeSpan(1, 0, 30)
Dim TS2 As New TimeSpan(0, 4, 12)
Debug.WriteLine((TS1 + TS2).TotalSeconds.ToString)

The TimeSpan class is discussed in detail in Chapter 11, "The Framework at Large," but for the purposes of the preceding example, all you need to know is that variable TS1 represents a time span of 1 hour and 30 seconds, while TS2 represents a time span of 4 minutes and 12 seconds. Their sum is a new time span of 1 hour, 4 minutes, and 42 seconds. So far you have seen how to overload methods and how the overloaded forms of a method can simplify development. Sometimes it makes sense to alter the default function of an operator. Let's say you designed a class for representing lengths in meters and centimeters, something like the following:

Dim MU As New MetricUnits
MU.Meters = 1
MU.Centimeters = 78

The MetricUnits class allows you to specify lengths as an integer number of meters and centimeters (presumably you don't need any more accuracy). The most common operation you'll perform with this class is to add and subtract lengths. However, you can't directly add two objects of the MetricUnits type by using a statement such as this:

TotalLength = MU1 + MU2

Wouldn't it be nice if you could add two custom objects by using the addition operator? For this to happen, you should be able to overload the addition operator, just as you can overload a method. Indeed, it's possible to overload an operator for your custom classes and write statements like the preceding one. Let's design a class to express lengths in metric and English units and then overload the basic operators for this class.

To overload an operator, you must create an Operator procedure, which is basically a function with an odd name: the name (this is usually a symbol) of the operator you want to overload. The Operator procedure accepts as arguments two values of the custom type (the type for which you're overloading the operator) and returns a value of the same type. Here's the outline of an Operator procedure that overloads the addition operator:

Public Shared Operator + (
           ByVal length1 As MetricUnits,
           ByVal length2 As MetricUnits) As MetricUnits
End Operator

The procedure's body contains the statements that add the two arguments as units of length, not as numeric values. Overloading operators is a straightforward process that can help you create elegant classes that can be manipulated with the common operators.

VB 2010 at Work: The LengthUnits Class

To demonstrate the overloading of common operators, I included the LengthUnits project, which is a simple class for representing distances in English and metric units. Listing 8.23 shows the definition of the MetricUnits class, which represents lengths in meters and centimeters.

Example 8.23. The MetricUnits class

Public Class MetricUnits
    Private _Meters As Integer
    Private _Centimeters As Integer
Public Sub New()

    End Sub

    Public Sub New(ByVal meters As Integer, ByVal centimeters As Integer)
        Me.Meters = meters
        Me.Centimeters = centimeters
    End Sub

    Public Property Meters As Integer

    Public Property Centimeters() As Integer

        Get
           Return _Centimeters
        End Get
        Set(ByVal Value As Integer)
            If value > 100 Then
               _Meters += Convert.ToInt32(Math.Floor(Value / 100))
               _Centimeters = (Value Mod 100)
            Else
               _Centimeters = value
            End If
        End Set
    End Property

    Public Overloads Function Tostring() As String
        Dim str As String = Math.Abs(_Meters).ToString & " meters, " &
                   Math.Abs(_Centimeters).ToString & " centimeters"
        If _Meters < 0 Or (_Meters = 0 And _Centimeters < 0) Then
            str = "-" & str
        End If
        Return str
    End Function
End Class

The class uses the private variables _Meters and _Centimeters to store the two values that determine the length of the current instance of the class. These variables are exposed as the Meters and Centimeters properties. Notice the two forms of the constructor and the custom ToString method. Because the calling application may supply a value that exceeds 100 for the Centimeters property, the code that implements the Centimeters property checks for this condition and increases the Meters property, if needed. It allows the calling application to set the Centimeters property to 252, but internally it increases the _Meters local variable by 2 and sets the _Centimenters local variable to 52. The ToString method returns the value of the current instance of the class as a string such as 1.98, but it inserts a minus sign in front of it if it's negative. If you open the sample project, you'll find the implementation of the EnglishUnits class, which represents lengths in feet and inches. The code is quite similar and I won't repeat it here.

There's nothing out of the ordinary so far; it's actually a trivial class. We can turn it into a highly usable class by overloading the basic operators for the MetricUnits class: namely the addition and subtraction operators. Add the Operator procedures shown in Listing 8.24 to the class's code to overload the addition (+) and subtraction (-) operators. By the way, you can't use these operators with variable of the Metric type; the compiler just doesn't know how to add two instances of this class.

Example 8.24. Overloading operators for the MetricUnits class

Public Shared Operator + (
                ByVal length1 As MetricUnits,
                ByVal length2 As MetricUnits) As MetricUnits
    Dim result As New metricUnits
    result.Meters = 0
    result.Centimeters =
        length1.Meters * 100 + length1.Centimeters +
        length2.Meters * 100 + length2.Centimeters
    Return result
End Operator

Public Shared Operator - (
                ByVal length1 As MetricUnits,
                ByVal length2 As MetricUnits) As MetricUnits
    Dim result As New MetricUnits
    result.Meters = 0
    result.Centimeters =
         length1.Meters * 100 + length1.Centimeters -
         length2.Meters * 100 - length2.Centimeters
    Return result
End Operator

These two procedures turn an ordinary class into an elegant custom data type. You can now create MetricUnits variables in your code and manipulate them with the addition and subtraction operators as if they were simple numeric data types. The following code segment exercises the MetricUnits class:

Dim MU1 As New MetricUnits
MU1.Centimeters = 194
Debug.WriteLine("194 centimeters is " & MU1.Tostring & " meters")
194 centimeters is 1.94 meters
Dim MU2 As New MetricUnits
MU2.Meters = 1
MU2.Centimeters = 189
Debug.WriteLine("1 meter and 189 centimeters is " & MU2.Tostring & " meters")
1 meter and 189 centimeters is 2.89 meters
Debug.WriteLine("194 + 289 centimeters is " & (MU1 + MU2).Tostring & " meters")
194 + 289 centimeters is 4.83 meters
Debug.WriteLine("194 - 289 centimeters is " & (MU1 - MU2).Tostring & " meters")
The negative of 1.94 is −1.94
MU1.Meters = 4
MU1.Centimeters = 63
Dim EU1 As EnglishUnits = CType(MU1, EnglishUnits)
Debug.WriteLine("4.62 meters are " & EU1.Tostring)
4.62 meters are 15' 2"
MU1 = CType(EU1, MetricUnits)
Debug.WriteLine(EU1.Tostring & " are " & MU1.Tostring & " meters")
15' 2" are 4.62 meters

If you execute the preceding statements, the highlighted values will appear in the Output window. (The LengthUnits sample project, available for download from www.sybex.com/go/masteringvb2010, uses a TextBox control to display its output.) Figure 8.9 shows the test project for the MetricUnits and EnglishUnits classes. The last few statements convert values between metric and English units, and you'll see the implementation of these operations momentarily.

Exercising the members of the MetricUnits class

Figure 8.9. Exercising the members of the MetricUnits class

Implementing Unary Operators

In addition to being the subtraction operator, the minus symbol is also a unary operator (it negates the following value). If you attempt to negate a MetricUnits variable, an error will be generated because the subtraction operator expects two values — one on either side of it. In addition to the subtraction operator (which is a binary operator because it operates on two values), we must define the negation operator (which is a unary operator because it operates on a single value). The unary minus operator negates the following value, so a new definition of the subtraction Operator procedure is needed. This definition will overload the existing one, as follows:

Public Overloads Shared Operator -(
          ByVal length1 As MetricUnits) As MetricUnits
    Dim result As New MetricUnits
    result.Meters = -length1.Meters
    result.Centimeters = -length1.Centimeters
    Return result
End Operator

To negate a length unit stored in a variable of the MetricUnits type in your application's code, use statements such as the following:

MU2 = -MU1
Debug.Write(MU2.Tostring)
Debug.Write((-MU1).Tostring)

Both statements will print the following in the Output window:

−1 meters, −94 centimeters

There are several unary operators, which you can overload in your custom classes as needed. There's the unary + operator (not a common operator), and the Not, IsTrue, and IsFalse operators, which are logical operators. The last unary operator is the CType operator, which is exposed as a method of the custom class and is explained next.

Handling Variants

To make your custom data type play well with the other data types, you must also provide a CType() function that can convert a value of the MetricUnits type to any other type. It doesn't make much sense to convert metric units to dates or any of the built-in objects, but let's say you have another class: the EnglishUnits class. This class is similar to the MetricUnits class, but it exposes the Inches and Feet properties in place of the Meters and Centimeters properties. The CType() function of the MetricUnits class, which will convert metric units to English units, is shown next:

Public Overloads Shared Widening Operator
                 CType(ByVal MU As MetricUnits) As EnglishUnits
    Dim EU As New EnglishUnits
    EU.Inches = Convert.ToInt32(
                (MU.Meters * 100 +  MU.Centimeters) / 2.54)
    Return EU
End Operator

Do you remember the implicit narrowing and widening conversions we discussed in Chapter 2, "Handling Data"? An attempt to assign an integer value to a decimal variable will produce a warning, but the statement will be executed because it's a widening conversion (no loss of accuracy will occur). The opposite is not true. If the Strict option is on, the compiler won't allow narrowing conversions because not all decimal values can be mapped to integers. To help the compiler enforce strict types, you can use the appropriate keyword to specify whether the CType() function performs a widening or a narrowing conversion. The CType() procedure is shared and overloads the default implementation, which explains all the keywords prefixing its declaration. The following statements exercise the CType method of the MetricUnits class:

Debug.Write(MU1.Tostring)
1 meters, 94 centimeters
Debug.WriteLine(CType(MU1, EnglishUnits).Tostring)
6 feet, 4 inches

The output of the two statements is highlighted. In this code both classes expose integer properties, so the Widening or Narrowing keyword isn't really important. In other situations, you must carefully specify the type of the conversion to help the compiler generate the appropriate warnings (or exceptions, if needed).

The CType operator we added to the MetricUnits class can only convert values of the MetricUnit type to values of the EnglishUnit type. If it makes sense to convert MetricUnits variables to other types, you must provide more overloaded forms of the CType() procedure. For example, you can convert them to numeric values (the numeric value could be the length in centimeters or a double value that represents the same length in meters). The compiler sees the return type(s) of the various overloaded forms of the CType operator, and since it knows whether the requested conversion is possible, it will generate the appropriate exception.

In short, operator overloading isn't complicated, adds a touch of elegance to a custom class, and enables variables of this type to mix well with the other data types. If you like math, you could implement classes to represent matrices, or complex numbers, and overload the usual operators for addition, multiplication, and so on. The downside of operator overloading (at least in its current implementation) is that it requires quite a bit of code. Even so, the code is straightforward. The LengthUnits sample application contains quite a bit of code that I haven't discussed in this chapter, but I suggest you take a look at the application as it implements many operators, including equals (=), not equals (<>), greater than, and less than.

The Bottom Line

Build your own classes.

Classes contain code that executes without interacting with the user. The class's code is made up of three distinct segments: the declaration of the private variables, the property procedures that set or read the values of the private variables, and the methods, which are implemented as public subroutines or functions. Only the public entities (properties and methods) are accessible by any code outside the class. Optionally, you can implement events that are fired from within the class's code. Classes are referenced through variables of the appropriate type, and applications call the members of the class through these variables. Every time a method is called, or a property is set or read, the corresponding code in the class is executed.

Master It

How do you implement properties and methods in a custom class?

Master It

How would you use a constructor to allow developers to create an instance of your class and populate it with initial data?

Master It

Which are the default methods of a custom class that you will most likely override with more meaningful definitions?

Master It

How should you handle exceptions in a class?

Overloading operators.

Overloading is a common theme in coding classes (or plain procedures) with Visual Basic. In addition to overloading methods, you can overload operators. In other words, you can define the rules for adding or subtracting two custom objects, if this makes sense for your application.

Master It

When should you overload operators in a custom class, and why?

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

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