The use of classes in OOP allows you to build an application from discrete components, each of which encapsulates the variables and methods needed to carry out specific tasks. But as powerful as classes are for designing and implementing applications, the ability to reuse and customize them is what makes support for OOP such a powerful tool in Visual Basic 2005. In this section, you'll see how you can create your own classes by modifying those provided by the .NET Framework. The same principles can also be applied to class libraries that you or your teams develop. In addition, you'll learn about the new generic classes in Visual Basic 2005 that are designed from scratch to be flexible in the range of types they accept. 3.2.1. Using InheritanceOne of the fundamental concepts of OOP is inheritance. Inheritance facilitates code reuse and allows you to extend and use the code that you have already written. Simply put, inheritance is the ability to extend the functionality of classes, and is the basis of the several techniques we discuss in this section. The beauty of inheritance is that you can define all the common logic you need in a single master class typically called the base or parent class and then use inheritance to extend its logic in a derived class or child class and customize it to suit your own needs. In this section, you will learn how you can inherit from the .NET Stack class, and in the next section you'll learn how you can customize it. First, let's create a new Windows application using Visual Studio 2005. Name the project MyStackApp. Add a new class item to MyStackApp by right-clicking on the project name in Solution Explorer and then selecting Add New Item…. Select Class and name the class Double-click the MyStack.vb file in Solution Explorer to open it for editing. In the MyStack class, use the Inherits keyword to inherit from the Stack class, as shown in the following snippet: Public Class MyStack Inherits System.Collections.Stack End Class By using the Inherits keyword, you specify that your MyStack class is to inherit all the methods and properties of the Stack class of the .NET Class Library. You can use MyStack in place of the Stack class. To do so, double-click on the default Form1 in Solution Explorer and then double-click on the design pane to generate the Form_Load event of Form1, as shown in Example 3-3. Enter the code as shown in bold. Example 3-3. Replacing the Stack class with the MyStack classPrivate Sub Form1_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load Dim ms1 As New MyStack ms1.Push("Hello ") ms1.Pop() End Sub 3.2.2. Customizing a MethodIn the last section, you saw how to create a customized class by deriving MyStack from the .NET Stack class and how to use it just like the Stack class. However, you may wish to tweak some of the methods available in the MyStack class to suit your own purposes. Like the car designer, you might not be pleased with the performance of the braking system of the previous model. Hence, you would want to redesign and fine-tune the braking system for the new model. Suppose you want to use a Stack to add two numbers. It turns out that the Push and Pop methods of the .NET class are not optimal for this task. Consider the following code snippet: s1.Push(5) s1.Push("S") MsgBox(s1.Pop + s1.Pop) The second Push method has pushed a String instead of an Integer onto the Stack. When you try to pop the two values (5 and S) from the stack and perform a mathematical operation on them, you will get a runtime error. This vulnerability arises from the fact that the Push method of Stack accepts an Object parameter. But of course, every .NET type is an object, so Push will accept any data type. If you know ahead of time that your stack will be used for arithmetic operations, it would be useful to restrict the parameters Push will accept to numbers only. Changing the behavior of the inherited Push method can be done by overriding it. You override a method in VB 2005 by defining a new version that suits your purposes and by indicating that you want to use this version instead of the inherited version with the Overrides keyword, as shown in Example 3-4. The first thing you will do is override the Push method in the Stack class. Recall that the Push method does not check for the type of the data pushed onto the stack. Assuming that you want the MyStack class to deal only with numeric values, you need to override the implementation of the original Push method with the Overrides keyword. Now the base method Push is no longer accessible. Example 3-4. Overriding the Push method of StackPublic Class MyStack Inherits System.Collections.Stack Public Overrides Sub Push(ByVal obj As Object) If Not IsNumeric(obj) Then Throw New Exception("Non-numeric value in Stack") End If MyBase.Push(obj) End Sub End Class The new Push method now checks to ensure that the value pushed into the stack is a numeric value; if it is not, the method throws an exception at runtime: ms1.Push(5) ms1.Push("S") ' runtime error If the value passed to the Push method is a number, the method calls the Push method of the base class, Stack. The MyBase keyword refers to the base class from which the current class is derived, and its inherited members. 3.2.3. Adding Alternate Versions of a Method to a ClassYour class can offer alternate version of the same method to its users. Adding alternate versions of the same method to a class is known as overloading and is yet another useful object-oriented technique available to VB 2005 programmers. Let's return to our stack example. In the previous section, you overrode the implementation of the original Push method so it will accept only numeric values. One drawback of this technique, however, is that IntelliSense will not explicitly show that numeric values are accepted (see Figure 3-1.). Figure 3-1. IntelliSense displaying the method signatureA better way would be to overload the Push method. With overloading, you can provide users with two versions of Push, each with a different signature. The signature of a method is determined by its parameter list. Two signatures are different when the data types or number of parameters in the parameter list are different. Example 3-5 shows how to add a new version of Push to MyStack. Example 3-5. Overloading the Push method of MyStackPublic Class MyStack Inherits System.Collections.Stack Public Overrides Sub Push(ByVal obj As Object) If Not IsNumeric(obj) Then Throw New Exception("Non-numeric value in Stack") End If MyBase.Push(obj) End Sub Public Overloads Sub Push(ByVal obj As Integer) MyBase.Push(obj) End Sub End Class
The Overloads keyword in Example 3-5 specifies that a procedure is a new version of an existing procedure with the same name. In addition to providing users with a version that accepts only integers, IntelliSense will now show that the Push method has two overloaded signatures, as shown in Figure 3-2. Figure 3-2. Push has two overloaded signatures3.2.4. Adding a New MethodSuppose you want your new car to be available to people with special needs, such as handicapped drivers. These drivers may not be able to use the conventional brake pedals, and hence, you might need to add a special brake pedal to the steering wheel. You can add new methods to a derived class to add functionality that's not there. For example, if you want users to push strings onto the Stack, you can define special string-friendly methods for the Push and Pop operations, as shown in Example 3-6.
Example 3-6. Adding new Push and Pop methods to MyStackPublic Class MyStack Inherits System.Collections.Stack Public Sub PushStr(ByVal obj As String) MyBase.Push(obj) End Sub Public Function PopStr() As String Return MyBase.Pop End Function End Class You can use the new methods as follows: Dim ms1 As New MyStack ms1.PushStr("Hello") MsgBox(ms1.PopStr()) 3.2.5. Customizing InitializationThe purpose of a constructor is to initialize the properties of an object when it is instantiated (i.e., created).
A constructor is a subroutine with the reserved name New. You can have as many constructors as you need so long as each has a different signature. Let's add two constructors to the MyStack class, as shown in Example 3-7. Example 3-7. Adding a custom constructor to MyStackPublic Class MyStack Inherits System.Collections.Stack Public Sub New() '---uses the base class constructor MyBase.New() End Sub Public Sub New(ByVal items() As Object) For i As Integer = 0 To items.Length - 1 MyBase.Push(items(i)) Next ' the following will also work: ' MyBase.New(items) End Sub … The first constructor does not accept an input parameter, and is hence known as the default constructor (or empty constructor) of the class. The second constructor takes one parameter, items, which is then used to populate the Stack. You can now use either of the two constructors to create an instance of the MyStack class, and the second constructor also initializes its content in a single statement, as shown in the following snippet: Dim itemsArray() As Object = {"Hello", "World"} Dim ms1 As New MyStack Dim ms2 As New MyStack(itemsArray) MsgBox(ms2.PopStr()) '---shows "World" MsgBox(ms2.PopStr()) '---shows "Hello"
3.2.6. Adding PropertiesThe .NET Stack class exposes a number of properties, such as Count, IsSynchronized, and SyncRoot. For example, the Count property returns the number of items in the Stack. You can add additional properties to the MyStack class by using the Property keyword. For example, you might want to expose a new CountNumeric property to return the number of items in the stack that are of numeric type. You may also want to add a Description property to add a description to the class. To do so, add the code shown in bold in Example 3-8. Example 3-8. Adding properties to MyStackPublic Class MyStack Inherits System.Collections.Stack Private _Description as String ReadOnly Property CountNumeric() As Integer Get Dim counter As Integer = 0 For Each o As Object In Me If (IsNumeric(o)) Then counter += 1 End If Next Return counter End Get End Property Property Description() As String Get Return _Description End Get Set(ByVal value As String) _Description = value End Set End Property …
The Set and Get accessors that you define allow you to assign and retrieve values from the properties of a class. Notice that the values set for the Description property are stored internally in the private variable _Description. The Private access modifier that precedes the declaration for _Description restricts the use of the variable to within the class. That is, they are not visible to code outside the class.
Here's a code snippet that uses the Set property procedure to assign values to the CountNumeric and Description properties and the Get property procedure to display them: Dim itemsArray() As Object = {"Visual", "Basic", 2005} Dim ms1 As New MyStack(itemsArray) ms1.Description = "This is my own Stack class!" MsgBox(ms1.CountNumeric) ' displays 1 MsgBox(ms1.Description) ' displays "This is my own Stack class!"
3.2.7. Weakly Typed Versus Strongly Typed VariablesWhen you declare a variable to be of a certain data type, it is said to be strongly typed. For example, a variable may be declared to be of Integer type. When you declare the data type of this variable, the Visual Basic compiler performs memory allocation for the Integer data type as well as optimizations before the program is executed. However, there are times when using a strongly typed variable isn't possible. As an example, the Push method of the Stack class accepts an item of type Object (see Figure 3-3). Figure 3-3. Pushing an Object into a StackYou can assign the value that you have popped from the Stack into an Object variable: Dim obj As Object obj = s1.Pop Because you can't determine what data type obj will be assigned to until runtime, in this case, obj is known as weakly typed. The downside to using weakly typed variables is that they are less efficient (and thus slower), as doing the type conversion at runtime chews up resources. Also, a code editor feature like IntelliSense in Visual Studio will not be able to take advantage of features accorded by early binding. Figure 3-4 shows that IntelliSense does not know the properties and methods available in obj until runtime. Figure 3-4. IntelliSense has no clue what data type obj is assigned toHowever, you can perform an explicit type conversion for IntelliSense to display the properties and methods available in obj using the CType function (see Figure 3-5). Figure 3-5. Performing an explicit type conversion using the CType functionIn contrast, in the MyStack class, you have added the PushStr method, which takes in a string data type, and the PopStr method, which returns a string data type. As the type of the variable is known before the program is run, the str variable is said to be strongly typed: Dim ms1 As New MyStack ms1.PushStr("This is a string") Dim str As String str = ms1.PopStr While using strongly typed variables has its disadvantages over weakly typed variables, using a weakly typed variable is useful in cases where you are writing generic code (such as the Stack class) and won't know the type of object you are working with until runtime. 3.2.8. Using a Generic ClassWith a class like Stack, which has many uses, it would be great if you could specify the data types to be handled by a particular instance at the time the Stack object is created. VB 2005 now supports a new feature known as generics and provides a number of so-called generic classes that anticipate, by design, that they will be customized before they are instantiated. Using generics, you can define classes that let you specify the data types a class accepts when the class is instantiated. To see the benefits of generics, let's revisit the Stack class that we have been discussing in the last few sections. Without generics, you are likely to find that you need to write multiple versions of the Stack class if you want the class to work with more than one data type say, integers, strings, or a complex Employee object. Using generics, you can now defer specifying the type of data that you want to use for your Stack until you actually instantiate a Stack object in your program. You'll find a new generic Stack class in the System.Collections.Generic namespace of the .NET Framework Class Library that allows you to specify during design time the data type you want to use. Here's how to declare that you want to push and pop integers on a stack: Dim s2 As New _ System.Collections.Generic.Stack(Of Integer) s2.Push(5) s2.Push(6) s2.Push("Some string…") ' error
By using the new Of keyword, you indicate the type of data you want to use with that class.
If you want to use the Stack class for String data types, you simply do this: Dim s2 As New _ System.Collections.Generic.Stack(Of String) s2.Push("VB2005 ") s2.Push("supports ") s2.Push("Generics") Besides using the generic classes in the .NET Framework, you can also write your own generic classes, a topic that is beyond the scope of this book. (For additional information, see Programming Visual Basic 2005, O'Reilly.) 3.2.9. Splitting Up the Physical Implementation of a ClassVB 2005 supports a new .NET 2.0 enhancement: partial classes. In a nutshell, with partial classes, you can now split your class definition into multiple physical files. Logically, partial classes do not make any difference to the compiler. During compile time, the Visual Basic compiler simply groups all the various partial classes together and treats them as a single entity.
One of the greatest benefits of partial classes is that they allow a clean separation of business logic and the user interface (in particular, the code that is generated by the visual designer in Visual Studio 2005). Using partial classes, the UI code can be hidden from the developer, who usually has no need to access it anyway. Partial classes also make debugging easier, as the code is partitioned into separate files.
Figure 3-6 shows the code behind of a Windows Form: Form1. Notice that no hidden Windows designer-generated code appears on the page (as would be visible in Visual Studio .NET 2003). The absence of that section allows you to concentrate on writing the business logic of your application and reduces the chances that you may inadvertently modify the code generated by the designer. Figure 3-6. The code behind of Form1If for some reason you need to access the Windows designer-generated code, you can go to Solution Explorer and click on the Show All Files button. There you will find a file named Form1.Designer.vb (see Figure 3-7) under the Form1.vb file. Figure 3-7. Revealing the Windows designer-generated code in Solution ExplorerFigure 3-8 shows the content of the Form1.Designer.vb file. In VB 2005, all of the partial classes except one must use the Partial keyword prefix; only one class may omit it. However, it is recommended that you always prefix all your partial classes with the Partial keyword. At least this will give you a visual clue that part of the implementation of the class lies somewhere else, and this is definitely useful when it comes to debugging. Figure 3-8. The content of the Windows designer-generated code
If your class implements many interfaces (see "Creating Contracts with Implementers Using Interfaces" for more details on interfaces), it is a good idea to use a partial class to contain the implementation for each interface. |