Advanced Concepts


So far, you’ve learned how to work with objects, how to create classes with methods, properties, and events, and how to use constructors. You’ve also learned how objects are destroyed within the .NET environment and how you can hook into that process to do any cleanup required by the objects.

Now let’s move on to discuss some more complex topics and variations on what has been discussed so far. First you’ll look at some advanced variations of the methods you can implement in classes, including an exploration of the underlying technology behind events.

Overloading Methods

Methods often accept parameter values. The Person object’s Walk() method, for instance, accepts an Integer parameter:

 Public Sub Walk(ByVal distance As Integer)   mTotalDistance += distance   RaiseEvent Walked(distance) End Sub

Sometimes there is no need for the parameter. To address this, you can use the Optional keyword to make the parameter optional:

 Public Sub Walk(Optional ByVal distance As Integer = 0)   mTotalDistance += distance   RaiseEvent Walked(distance) End Sub

This doesn’t provide you with a lot of flexibility, however, as the optional parameter or parameters must always be the last ones in the list. In addition, all this allows you to do is choose to pass or not to pass the parameter. Suppose that you want to do something fancier, such as allow different datatypes, or even entirely different lists of parameters.

Tip 

Note that use of the Optional keyword makes the code harder to consume from C# or other .NET languages because they don’t support optional parameters as VB does. If you are only working in Visual Basic, this may be a non-issue, but if you are working in a multilanguage environment, avoid using the Optional keyword. In addition, optional parameters require a default value.

Method overloading provides exactly those capabilities. By overloading methods, you can create several methods of the same name, with each one accepting a different set of parameters, or parameters of different datatypes.

As a simple example, instead of using the Optional keyword in the Walk() method, you could use overloading. You keep the original Walk() method, but you also add another Walk() method that accepts a different parameter list. Change the code in the Person class back to the following:

 Public Sub Walk(ByVal distance As Integer)   mTotalDistance += distance   RaiseEvent Walked(distance) End Sub

Then you can create another method with the same name, but with a different parameter list (in this case, no parameters). Add this code to the class, without removing or changing the existing Walk() method:

  Public Sub Walk()   RaiseEvent Walked(0) End Sub  

At this point, you have two Walk() methods. The only way to tell them apart is by the list of parameters each accepts: the first requiring a single Integer parameter, the second having no parameter.

Tip 

There is an Overloads keyword as well. This keyword is not needed for simple overloading of methods as described here, but it is required when combining overloading and inheritance. This is discussed in Chapter 4.

You have the option of calling the Walk method in a couple of different ways. You can call it with a parameter:

 objPerson.Walk(42)

Or without a parameter:

 objPerson.Walk()

You can have any number of Walk() methods in the class as long as each individual Walk() method has a different method signature.

Method Signatures

All methods have a signature, which is defined by the method name and the datatypes of its parameters:

  Public Function CalculateValue() As Integer End Sub 

In this example, the signature is f(). The letter f is often used to indicate a method or function. It is appropriate here because you don’t care about the name of the function; only its parameter list is important.

If you add a parameter to the method, then the signature changes. For instance, you could change the method to accept a Double:

  Public Function CalculateValue(ByVal value As Double) As Integer 

In that case, the signature of the method is f(Double).

Notice that in Visual Basic the return value is not part of the signature. You can’t overload a Function routine by just having its return value’s datatype vary. It is the datatypes in the parameter list that must vary to utilize overloading.

Also note that the name of the parameter is totally immaterial; only the datatype is important. This means that the following methods have identical signatures:

 Public Sub DoWork(ByVal x As Integer, ByVal y As Integer) Public Sub DoWork(ByVal value1 As Integer, ByVal value2 As Integer)

In both cases, the signature is f(Integer, Integer).

The datatypes of the parameters define the method signature, but whether the parameters are passed ByVal or ByRef does not. Changing a parameter from ByVal to ByRef will not change the method signature.

Combining Overloading and Optional Parameters

Overloading is more flexible than using optional parameters, but optional parameters have the advantage that they can be used to provide default values, as well as making a parameter optional.

You can combine the two concepts: overloading a method and having one or more of those methods utilize optional parameters. Obviously, this sort of thing can become very confusing if overused, as you’re employing two types of method “overloading” at the same time.

The Optional keyword causes a single method to effectively have two signatures. This means that a method declared as

 Public Sub DoWork(ByVal x As Integer, Optional ByVal y As Integer = 0)

has two signatures at once: f(Integer, Integer) and f(Integer).

Because of this, when you use overloading along with optional parameters, the other overloaded methods cannot match either of these two signatures. However, as long as other methods don’t match either signature, you can use overloading, as discussed earlier. For instance, you could implement methods with the following different signatures:

 Public Sub DoWork(ByVal x As Integer, Optional ByVal y As Integer = 0)

and

 Public Sub DoWork(ByVal data As String)

because there are no conflicting method signatures. In fact, with these two methods, you’ve really created three signatures:

  • f(Integer, Integer)

  • f(Integer)

  • f(String)

The IntelliSense built into the Visual Studio IDE will indicate that you have two overloaded methods, one of which has an optional parameter. This is different from creating three different overloaded methods to match these three signatures, in which case the IntelliSense would list three variations on the method from which you could choose.

Overloading Constructor Methods

In many cases, you may want the constructor to accept parameter values for initializing new objects, but also want to have the capability to create objects without providing those values. This is possible through method overloading, which is discussed later, or through the use of optional parameters.

Optional parameters on a constructor method follow the same rules as optional parameters for any other Sub routine; they must be the last parameters in the parameter list, and you must provide default values for the optional parameters.

For instance, you can change the Person class as shown here:

 Public Sub New(Optional ByVal name As String = "", _     Optional ByVal birthDate As Date = #1/1/1900#)   mName = name   mBirthDate = birthDate   Phone("home") = "555-1234"   Phone("work") = "555-5678" End Sub

Here, you’ve changed both the Name and BirthDate parameters to be optional, and you are providing default values for both of them. Now you have the option of creating a new Person object with or without the parameter values:

 Dim myPerson As New Person("Peter", "1/1/1960")

or

 Dim myPerson As New Person()

If you don’t provide the parameter values, then the default values of an empty String and 1/1/1900 will be used and the code will work just fine.

Overloading the Constructor Method

You can combine the concept of a constructor method with method overloading to allow for different ways of creating instances of the class. This can be a very powerful combination, because it allows a great deal of flexibility in object creation.

You’ve already explored how to use optional parameters in the constructor. Now let’s change the implementation in the Person class to make use of overloading instead. Change the existing New() method as follows:

 Public Sub New(ByVal name As String, ByVal birthDate As Date)     mName = name     mBirthDate = birthDate     Phone("home") = "555-1234"     Phone("work") = "555-5678"   End Sub

With this change, you require the two parameter values to be supplied. Now add that second implementation, as shown here:

  Public Sub New()   Phone("home") = "555-1234"   Phone("work") = "555-5678" End Sub 

This second implementation accepts no parameters, meaning that you can now create Person objects in two different ways - either with no parameters or by passing the name and birth date:

 Dim myPerson As New Person()

or

 Dim myPerson As New Person("Fred", "1/11/60")

This type of capability is very powerful, because it enables you to define the various ways in which applications can create objects. In fact, the Visual Studio IDE takes this into account, so when you are typing the code to create an object, the IntelliSense tooltip will display the overloaded variations on the method, providing a level of automatic documentation for the class.

Shared Methods, Variables, and Events

So far, all of the methods you’ve built or used have been instance methods, methods that require you to have an actual instance of the class before they can be called. These methods have used instance variables or member variables to do their work, which means that they have been working with a set of data that is unique to each individual object.

With Visual Basic you can create variables and methods that belong to the class, rather than to any specific object. In other words, these variables and methods belong to all objects of a given class and are shared across all the instances of the class.

You can use the Shared keyword to indicate which variables and methods belong to the class, rather than to specific objects. For instance, you may be interested in knowing the total number of Person objects created as the application is running - kind of a statistical counter.

Shared Variables

Because regular variables are unique to each individual Person object, they don’t enable you to easily track the total number of Person objects ever created. However, if you had a variable that had a common value across all instances of the Person class, you could use that as a counter. Add the following variable declaration to the Person class:

 Public Class Person   Implements IDisposable    Private Shared mCounter As Integer  

By using the Shared keyword, you are indicating that this variable’s value should be shared across all Person objects within your application. This means that if one Person object makes the value 42, then all other Person objects will see the value as 42: It is a shared piece of data.

You can now use this variable within the code. For instance, you can add code to the constructor method, New(), to increment the variable so that it acts as a counter - adding 1 each time a new Person object is created. Change the New() methods as shown here:

 Public Sub New()   Phone("home") = "555-1234"   Phone("work") = "555-5678"   mCounter += 1 End Sub Public Sub New(ByVal name As String, ByVal birthDate As Date)   mName = name   mBirthDate = birthDate   Phone("home") = "555-1234"   Phone("work") = "555-5678"   mCounter += 1 End Sub

The mCounter variable will now maintain a value indicating the total number of Person objects created during the life of the application. You may want to add a property routine to allow access to this value by writing the following code:

  Public ReadOnly Property PersonCount() As Integer   Get     Return mCounter   End Get End Property 

Notice that you’re creating a regular property that returns the value of a shared variable. This is perfectly acceptable. As you’ll see shortly, you could also choose to create a shared property to return the value.

Now you could write code to use the class as follows:

 Dim myPerson As Person myPerson = New Person() myPerson = New Person() myPerson = New Person() Messagebox.Show(myPerson.PersonCount)

The resulting display would show 3, because you’ve created three instances of the Person class. You would also need to decrement the counter after the objects are destroyed.

Shared Methods

You can share not only variables across all instances of a class, but also methods. Whereas a regular method or property belongs to each specific object, a shared method or property is common across all instances of the class. There are a couple of ramifications to this approach.

First, because shared methods don’t belong to any specific object, they can’t access any instance variables from any objects. The only variables available for use within a shared method are shared variables, parameters passed into the method, or variables declared locally within the method itself. If you attempt to access an instance variable within a shared method, you’ll get a compiler error.

In addition, because shared methods are actually part of the class rather than any object, you can write code to call them directly from the class without having to create an instance of the class first.

For instance, a regular instance method is invoked from an object:

 Dim myPerson As New Person() myPerson.Walk(42)

However, a shared method can be invoked directly from the class itself:

 Person.SharedMethod()

This saves the effort of creating an object just to invoke a method, and can be very appropriate for methods that act on shared variables, or methods that act only on values passed in via parameters. You can also invoke a shared method from an object, just like a regular method. Shared methods are flexible in that they can be called with or without creating an instance of the class first.

To create a shared method, you again use the Shared keyword. For instance, the PersonCount property created earlier could easily be changed to become a shared method instead:

 Public Shared ReadOnly Property PersonCount() As Integer   Get     Return mCounter   End Get End Property

Because this property returns the value of a shared variable, it is perfectly acceptable for it to be implemented as a shared method. With this change, you can now determine how many Person objects have ever been created without having to actually create a Person object first:

 Messagebox.Show(CStr(Person.PersonCount))

As another example, in the Person class, you could create a method that compares the ages of two people. Add a shared method with the following code:

  Public Shared Function CompareAge(ByVal person1 As Person, _     ByVal person2 As Person) As Boolean     Return person1.Age > person2.Age End Function 

This method simply accepts two parameters - each a Person - and returns True if the first is older than the second. Use of the Shared keyword indicates that this method doesn’t require a specific instance of the Person class in order for you to use it.

Within this code, you are invoking the Age property on two separate objects, the objects passed as parameters to the method. It is important to recognize that you’re not directly using any instance variables within the method; rather, you are accepting two objects as parameters, and invoking methods on those objects. To use this method, you can call it directly from the class:

 If Person.CompareAge(myPerson1, myPerson2) Then

Alternately, you can also invoke it from any Person object:

 Dim myPerson As New Person() If myPerson.CompareAge(myPerson, myPerson2) Then

Either way, you’re invoking the same shared method, and you’ll get the same behavior, whether you call it from the class or a specific instance of the class.

Shared Properties

As with other types of methods, you can also have shared property methods. Properties follow the same rules as regular methods. They can interact with shared variables but not member variables. They can also invoke other shared methods or properties, but can’t invoke instance methods without first creating an instance of the class. You can add a shared property to the Person class with the following code:

  Public Shared ReadOnly Property RetirementAge() As Integer   Get     Return 62   End Get End Property 

This simply adds a property to the class that indicates the global retirement age for all people. To use this value, you can simply access it directly from the class:

 Messagebox.Show(Person.RetirementAge)

Alternately, you can access it from any Person object:

 Dim myPerson As New Person() Messagebox.Show(myPerson.RetirementAge)

Either way, you’re invoking the same shared property.

Shared Events

As with other interface elements, events can also be marked as Shared. For instance, you could declare a shared event in the Person class:

  Public Shared Event NewPerson() 

Shared events can be raised from both instance methods and shared methods. Regular events cannot be raised by shared methods. Because shared events can be raised by regular methods, you can raise this one from the constructors in the Person class:

 Public Sub New()   Phone("home") = "555-1234"   Phone("work") = "555-5678"   mCounter += 1   RaiseEvent NewPerson() End Sub Public Sub New(ByVal name As String, ByVal birthDate As Date)   mName = Name   mBirthDate = BirthDate   Phone("home") = "555-1234"   Phone("work") = "555-5678"   mCounter += 1   RaiseEvent NewPerson() End Sub

The interesting thing about receiving shared events is that you can get them from either an object, such as a normal event, or from the class itself. For instance, you can use the AddHandler method in the form’s code to catch this event directly from the Person class.

First, let’s add a method to the form to handle the event:

  Private Sub OnNewPerson()   Messagebox.Show("new person " & Person.PersonCount) End Sub 

Then, in the form’s Load event, add a statement to link the event to this method:

 Private Sub Form1_Load(ByVal sender As System.Object, _     ByVal e As System.EventArgs) Handles MyBase.Load      AddHandler Person.NewPerson, AddressOf OnNewPerson     mPerson = New Person()     If Microsoft.VisualBasic.Command = "nodisplay" Then       AddHandler mPerson.Walked, AddressOf LogOnWalk     Else       AddHandler mPerson.Walked, AddressOf OnWalk     End If End Sub

Notice that you are using the class, rather than any specific object in the AddHandler statement. You could use an object as well, treating this like a normal event, but this illustrates how a class itself can raise an event. When you run the application now, anytime a Person object is created you’ll see this event raised.

Shared Constructor

A class can also have a Shared constructor:

 Shared Sub New() End Sub

Normal constructors are called when an instance of the class is created. The Shared constructor is only called once during the lifetime of an application, immediately before any use of the class.

This means that the Shared constructor is called before any other Shared methods, and before any instances of the class are created. The first time any code attempts to interact with any method on the class, or attempts to create an instance of the class, the Shared constructor is invoked.

Because you never directly call the Shared constructor, it can’t accept any parameters. Moreover, because it is a Shared method, it can only interact with Shared variables or other Shared methods in the class.

Typically, a Shared constructor is used to initialize Shared fields within an object. In the Person class for instance, you can use it to initialize the mCount variable:

  Shared Sub New()   mCount = 0 End Sub 

Because this method is only called once during the lifetime of the application, it is safe to do one-time initializations of values in this constructor.

Operator Overloading

Many basic datatypes, such as Integer and String, support the use of operators including +, -, =, <>, and so forth. When you create a class, you are defining a new type, and sometimes it is appropriate for types to also support the use of operators.

In your class, you can write code to define how each of these operators works when applied to objects. What does it mean when two objects are added together? Or multiplied? Or compared? If you can define what these operations mean, you can write code to implement appropriate behaviors. This is called operator overloading, as you are overloading the meaning of specific operators.

Operator overloading is done by using the Operator keyword, much in the same way that you create a Sub, Function, or Property method.

Most objects at least provide for some type of comparison, and so will often overload the comparison operators (=, <>, and maybe <, >, <=, and >=). You can do this in the Person class, for example, by adding the following code:

  Public Shared Operator =(ByVal person1 As Person, _   ByVal person2 As Person) As Boolean   Return person1.Name = person2.Name End Operator Public Shared Operator <>(ByVal person1 As Person, _   ByVal person2 As Person) As Boolean   Return person1.Name <> person2.Name End Operator 

Note that you overload both the = and <> operators. Many operators come in pairs, and this includes the equality operator. If you overload =, then you must overload <> or a compiler error will result. Now that you’ve overloaded these operators, you can write code in Form1 such as the following:

 Dim p1 As New Person("Fred", #1/1/1960#) Dim p2 As New Person("Mary", #1/1/1980#) Dim p3 As Person = p1 Debug.WriteLine(CStr(p1 = p2)) Debug.WriteLine(CStr(p1 = p3))

Normally, it would be impossible to compare two objects using a simple comparison operator, but because you’ve overloaded the operator, this becomes valid code. The result will display False and True.

Both the = and <> operators accept two parameters, so these are called binary operators. There are also unary operators that accept a single parameter. For instance, you might define the ability to convert a String value into a Person object by overloading the CType operator:

  Public Shared Narrowing Operator CType(ByVal name As String) As Person   Dim obj As New Person   obj.Name = name   Return obj End Operator 

To convert a String value to a Person, you assume that the value should be the Name property. You create a new object, set the Name property, and return the result. Because String is a broader, or less-specific, type than Person, this is a Narrowing conversion. Were you to do the reverse, convert a Person to a String, that would be a Widening conversion:

  Public Shared Widening Operator CType(ByVal person As Person) As String   Return person.Name End Operator 

Few non-numeric objects will overload most operators. It is difficult to imagine the result of adding, subtracting, or dividing two Customer objects against each other. Likewise, it is difficult to imagine performing bitwise comparisons between two Invoice objects. The following chart lists the various operators that can be overloaded:

Open table as spreadsheet

Operators

Meaning

=, <>

Equality and inequality. These are binary operators to support the a = b and a <> b syntax. If you implement one, then you must implement both.

>, <

Greater than and less than. These are binary operators to support the a > b and a < b syntax. If you implement one, then you must implement both.

>=, <=

Greater than or equal to and less than or equal to. These are binary operators to support the a >= b and a <= b syntax. If you implement one, then you must implement both.

IsFalse, IsTrue

Boolean conversion. These are unary operators to support the AndAlso and OrElse statements. The IsFalse operator accepts a single object and returns False if the object can be resolved to a False value. The IsTrue operator accepts a single value and returns True if the object can be resolved to a True value. If you implement one, then you must implement both.

CType

Type conversion. This is a unary operator to support the CType(a) statement. The CType operator accepts a single object of another type and converts that object to the type of your class. This operator must be marked as Narrowing to indicate that the type is more specific than the original type, or Widening to indic ate that the type is broader than the original type.

+, -

Addition and subtraction. These operators can be unary or binary. The unary form exists to support the a += b and a -= b syntax, while the binary form exists to support a + b and a b.

*, /, \, ^, Mod

Multiplication, division, exponent, and Mod. These are binary operators to support the a * b, a / b, a \ b, a ^ b, and a Mod b syntax.

&

Concatenation. This binary operator supports the a & b syntax. While this operator is typically associated with String manipulation, the & operator is not required to accept or return String values, so it can be used for any concatenation operation that is meaningful for your object type.

<<, >>

Bit shifting. These binary operators support the a << b and a >> b syntax. The second parameter of these operators must be a value of type Integer, which will be the integer value to be bit shifted based on your object value.

And, Or, Xor

Logical comparison or bitwise operation. These binary operators support the a And b, a Or b, and a Xor b syntax. If the operators return Boolean results, then they are performing logical comparisons. If they return results of other datatypes, then they are performing bitwise operations.

Like

Pattern comparison. This binary operator supports the a Like b syntax.

If an operator is meaningful for your datatype, then you are strongly encouraged to overload that operator.

Defining AndAlso and OrElse

Notice that neither the AndAlso nor the OrElse operators can be directly overloaded. This is because these operators use other operators behind the scenes to do their work. To overload AndAlso and OrElse, you need to overload a set of other operators, as shown here:

Open table as spreadsheet

AndAlso

OrElse

Overload the And operator to accept two parameters of your object’s type and to return a result of your object’s type.

Overload the Or operator to accept two parameters of your object’s type and to return a result of your object’s type.

Overload IsFalse for your object’s type (meaning that you can return True or False by evaluating a single instance of your object).

Overload IsTrue for your object’s type (meaning that you can return True or False by evaluating a single instance of your object).

If these operators are overloaded in your class, then you can use AndAlso and OrElse to evaluate statements that involve instances of your class.

Delegates

There are times when it would be nice to be able to pass a procedure as a parameter to a method. The classic scenario is when building a generic sort routine, where you need to provide not only the data to be sorted, but also a comparison routine appropriate for the specific data.

It is easy enough to write a sort routine that sorts Person objects by name, or to write a sort routine that sorts SalesOrder objects by sales date. However, if you want to write a sort routine that can sort any type of object based on arbitrary sort criteria, that gets pretty difficult. At the same time, because some sort routines can get very complex, it would be nice to reuse that code without having to copy and paste it for each different sort scenario.

By using delegates, you can create such a generic routine for sorting; and in so doing, you can see how delegates work and can be used to create many other types of generic routines. The concept of a delegate formalizes the process of declaring a routine to be called and calling that routine.

Tip 

The underlying mechanism used by the .NET environment for callback methods is the delegate. Visual Basic uses delegates behind the scenes as it implements the Event, RaiseEvent, WithEvents, and Handles keywords.

Declaring a Delegate

In your code, you can declare what a delegate procedure must look like from an interface standpoint. This is done using the Delegate keyword. To see how this works, let’s create a routine to sort any kind of data.

To do this, you’ll declare a delegate that defines a method signature for a method that compares the value of two objects and returns a Boolean indicating whether the first object has a larger value than the second object. You’ll then create a sort algorithm that uses this generic comparison method to sort data. Finally, you’ll create an actual method that implements the comparison, and then you’ll pass the address of that method to the sort routine.

Add a new module to the project by choosing the Project image from book Add Module menu option. Name the module Sort.vb, and then add the following code:

 Module Sort    Public Delegate Function Compare(ByVal v1 As Object, ByVal v2 As Object) _      As Boolean End Module

This line of code does something interesting. It actually defines a method signature as a datatype. This new datatype is named Compare, and it can be used within the code to declare variables or parameters that are accepted by your methods. A variable or parameter declared using this datatype can actually hold the address of a method that matches the defined method signature, and you can then invoke that method by using the variable.

Any method with the signature

 f(Object, Object)

can be viewed as being of type Compare.

Using the Delegate Datatype

You can write a routine that accepts this datatype as a parameter, meaning that anyone calling your routine must pass the address of a method that conforms to this interface. Add the following sort routine to the code module:

  Public Sub DoSort(ByVal theData() As Object, ByVal greaterThan As Compare)   Dim outer As Integer   Dim inner As Integer   Dim temp As Object   For outer = 0 To UBound(theData)   For inner = outer + 1 To UBound(theData)      If greaterThan.Invoke(theData(outer), theData(inner)) Then       temp = theData(outer)       theData(outer) = theData(inner)       theData(inner) = temp     End If    Next   Next End Sub 

The GreaterThan parameter is a variable that holds the address of a method matching the method signature defined by the Compare delegate. The address of any method with a matching signature can be passed as a parameter to your Sort routine.

Note the use of the Invokemethod, which is how a delegate is called from the code. Also note that the routine deals entirely with the generic System.Objectdatatype, rather than with any specific type of data. The specific comparison of one object to another is left to the delegate routine that is passed in as a parameter.

Implementing a Delegate Method

All that remains is to actually create the implementation of the delegate routine and call the sort method. On a very basic level, all you need to do is create a method that has a matching method signature. For instance, you could create a method such as the following:

 Public Function PersonCompare(ByVal person1 As Object, _   ByVal person2 As Object) As Boolean End Function

The method signature of this method exactly matches what you defined by your delegate earlier:

 Compare(Object, Object)

In both cases, you’re defining two parameters of type Object.

Of course, there’s more to it than simply creating the stub of a method. The method needs to return a value of True if its first parameter is greater than the second parameter. Otherwise, it should be written to deal with some specific type of data.

The Delegate statement defines a datatype based on a specific method interface. To call a routine that expects a parameter of this new datatype, it must pass the address of a method that conforms to the defined interface.

To conform to the interface, a method must have the same number of parameters with the same datatypes as defined in your Delegate statement. In addition, the method must provide the same return type as defined. The actual name of the method doesn’t matter; it is the number, order, and datatype of the parameters and the return value that count.

To find the address of a specific method, you can use the AddressOf operator. This operator returns the address of any procedure or method, allowing you to pass that value as a parameter to any routine that expects a delegate as a parameter.

The Person class already has a shared method named CompareAge that generally does what you want. Unfortunately, it accepts parameters of type Person, rather than of type Object as required by the Compare delegate. You can use method overloading to solve this problem.

Create a second implementation of CompareAge that accepts parameters of type Object as required by the delegate, rather than of type Person as shown in the existing implementation:

  Public Shared Function CompareAge(ByVal person1 As Object, _     ByVal person2 As Object) As Boolean   Return CType(person1, Person).Age > CType(person2, Person).Age End Function 

This method simply returns True if the first Person object’s age is greater than the second’s. The routine accepts two Object parameters, rather than specific Person type parameters, so you have to use the CType() method to access those objects as type Person. You accept the parameters as type Object because that is what is defined by the Delegate statement. You are matching its method signature:

 f(Object, Object)

Because this method’s parameter datatypes and return value match the delegate, you can use it when calling the Sort routine. Place a button on the form and write the following code behind that button:

  Private Sub Button2_Click(ByVal sender As System.Object, _     ByVal e As System.EventArgs) Handles button2.Click   Dim myPeople(4) As Person   myPeople(0) = New Person("Fred", #7/9/1960#)   myPeople(1) = New Person("Mary", #1/21/1955#)   myPeople(2) = New Person("Sarah", #2/1/1960#)   myPeople(3) = New Person("George", #5/13/1970#)   myPeople(4) = New Person("Andre", #10/1/1965#)   DoSort(myPeople, AddressOf Person.CompareAge) End Sub 

This code creates an array of Person objects and populates them. It then calls the DoSort routine from the module, passing the array as the first parameter, and the address of the shared CompareAge method as the second parameter. To display the contents of the sorted array in the IDE’s output window, you can add the following code:

 Private Sub button2_Click(ByVal sender As System.Object, _     ByVal e As System.EventArgs) Handles button2.Click    Dim myPeople(4) As Person    myPeople(0) = New Person("Fred", #7/9/1960#)    myPeople(1) = New Person("Mary", #1/21/1955#)    myPeople(2) = New Person("Sarah", #2/1/1960#)    myPeople(3) = New Person("George", #5/13/1970#)    myPeople(4) = New Person("Andre", #10/1/1965#)    DoSort(myPeople, AddressOf Person.CompareAge)      Dim myPerson As Person    For Each myPerson In myPeople      System.Diagnostics.Debug.WriteLine(myPerson.Name & " " & myPerson.Age)    Next End Sub

When you run the application and click the button, the output window displays a list of the people, sorted by age, as shown in Figure 3-5.

image from book
Figure 3-5

What makes this so very powerful is that you can change the comparison routine without changing the sort mechanism. Simply add another comparison routine to the Person class:

  Public Shared Function CompareName(ByVal person1 As Object, _     ByVal person2 As Object) As Boolean   Return CType(person1, Person).Name > CType(person2, Person).Name End Function 

Then, change the code behind the button on the form to use that alternate comparison routine:

 Private Sub Button2_Click(ByVal sender As System.Object, _     ByVal e As System.EventArgs) Handles Button2.Click    Dim myPeople(4) As Person    myPeople(0) = New Person("Fred", #7/9/1960#)    myPeople(1) = New Person("Mary", #1/21/1955#)    myPeople(2) = New Person("Sarah", #2/1/1960#)    myPeople(3) = New Person("George", #5/13/1970#)    myPeople(4) = New Person("Andre", #10/1/1965#)    DoSort(myPeople, AddressOf Person.CompareName)    Dim myPerson As Person    For Each myPerson In myPeople      System.Diagnostics.Debug.WriteLine(myPerson.Name & " " & myPerson.Age)    Next End Sub

When you run this updated code, you’ll find that the array contains a set of data sorted by name, rather than by age, as shown in Figure 3-6.

image from book
Figure 3-6

Simply by creating a new compare routine and passing it as a parameter, you can entirely change the way that the data is sorted. Better still, this sort routine can operate on any type of object, as long as you provide an appropriate delegate method that knows how to compare that type of object.

Classes versus Components

Visual Basic has another concept that is very similar to a class, the component. In fact, you can pretty much use a component and a class interchangeably, though there are some differences.

A component is really little more than a regular class, but it is one that supports a graphical designer within the Visual Studio IDE. This means that you can use drag-and-drop to provide the code in the component with access to items from the Server Explorer or from the toolbox.

To add a component to a project, select the Project image from book Add Component menu option, give the component a name, and click Open in the Add New Item dialog box.

When you add a class to the project, you are presented with the Code window. When you add a component, you are presented with a graphical designer surface, much like what you’d see when adding a Web Form to the project.

If you switch to the code view (by right-clicking in the Designer view and choosing View Code), you will see the code that is created automatically, just as it is with a Windows Form, Web Form, or regular class:

 Public Class Component1 End Class

This isn’t a lot more code than you’d see with a regular class, though there are differences behind the scenes. A component uses the same partial class technology as Windows Forms or Web Forms. This means that the code here is only part of the total code in the class. The rest of the code is hidden behind the designer’s surface and is automatically created and managed by Visual Studio.

In the designer code is an Inherits statement that makes every component inherit from System .ComponentModel.Component. Chapter 4 discusses the concept of inheritance, but note here that this Inherits line is what brings in all the support for the graphical designer in Visual Studio.

The designer also manages any controls or components that are dropped on the designer. Those controls or components are automatically made available to your code. For instance, if you drag and drop a Timer control from the Windows Forms tab of the toolbox onto the component, it will be displayed in the designer.

From here, you can set its properties using the standard Properties window in the IDE, just as you would for a control on a form. Using the Properties window, set the Name property to theTimer. You now automatically have access to a Timer object named theTimer, simply by dragging and dropping and setting some properties.

This means that you can write code within the component, just as you might in a class, to use this object:

  Public Sub Start()   theTimer.Enabled = True End Sub Public Sub Stop()   theTimer.Enabled = False End Sub Private Sub theTimer_Tick(ByVal sender As System.Object, _   ByVal e As System.EventArgs) Handles theTimer.Tick   ' do work End Sub 

For the most part, you can use a component interchangeably with a basic class, but using a component also provides some of the designer benefits of working with Windows Forms or Web Forms.




Professional VB 2005 with. NET 3. 0
Professional VB 2005 with .NET 3.0 (Programmer to Programmer)
ISBN: 0470124709
EAN: 2147483647
Year: 2004
Pages: 267

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