Reference Types (Classes)


A lot of the power of Visual Basic is harnessed in objects. An object is defined by its class, which describes what data, methods, and other attributes an instance of that class supports. Thousands of classes are provided in the .NET Framework class library.

When code instantiates an object from a class, the object created is a reference type. Recall that the data contained in value and reference types is stored in different locations, but this is not the only difference between them. A class (which is the typical way to refer to a reference type) has additional capabilities, such as support for protected methods and properties, enhanced event-handling capabilities, constructors, and finalizers, and can be extended with a custom base class via inheritance. Classes can also be used to define how operators such as “=” and “+” work on an instance of the class.

The intention of this chapter is to introduce you to some commonly used classes, and to complement your knowledge of the common value types already covered. Chapters 3 and 4 contain a detailed look at object orientation in Visual Basic. This chapter examines the features of the Object, String, DBNull, and Array classes, as well as the Collection classes found in the System.Collections namespace.

The Object Class

The Object class is the base class for every type in .NET, both value and reference types. At its core, every variable is an object and can be treated as such. You can think of the Object class (in some ways) as the replacement for the Variant type found in COM and COM-based versions of Visual Basic, but take care. COM, a Variant type, represents a variant memory location; in Visual Basic, an Object type represents a reference to an instance of the Object class. In COM, a Variant is implemented to provide a reference to a memory area on the heap, but its definition doesn’t define any specific ways of accessing this data area. As you’ll see during this look at objects, an instance of an “object” includes all the information needed to define the actual type of that object.

Because the Object class is the basis of all types, you can assign any variable to an object. Reference types maintain their current reference and implementation but are generically handled, whereas value types are packaged into a box and placed into the memory location associated with the Object. For example, there are instance methods that are available on Object, such as ToString. This method, if implemented, returns a string representation of an instance value. Because the Object class defines it, it can be called on any object:

  Dim objMyClass as New MyClass("Hello World") Console.WriteLine(objMyClass.ToString) 

This brings up the question of how the Object class knows how to convert custom classes to String objects. The answer is that it doesn’t. For this method to actually return the data in an instance of a String, a class must override this method. Otherwise, when this code is run, the default version of this method defined at the Object level returns the name of the current class (MyClass) as its string representation. This section will be clearer after you read Chapter 3. The key point is that if you create an implementation of ToString() in your class definition, then even when an instance of your object is cast to the type Object, your custom method will still be called. The following snippet shows how to create a generic object under the Option Strict syntax:

  Dim objVar as Object objVar = Me CType(objVar, Form).Text = "New Dialog Title Text" 

That Object is then assigned a copy of the current instance of a Visual Basic form. In order to access the Text property of the original Form class, the Object must be cast from its declared type of Object to its actual type (Form), which supports the Text property. The CType command (covered later) accepts the object as its first parameter, and the class name (without quotes) as its second parameter. In this case, the current instance variable is of type Form, and by casting this variable, the code can reference the Text property of the current form.

The String Class

Another class that plays a large role in most development projects is the String class. Having Strings defined as a class is more powerful than the Visual Basic 6.0 datatype of String that you may be more familiar with. The String class is a special class within .NET, because it is the one primitive type that is not a value type. To make String objects compatible with some of the underlying behavior in .NET, they have some interesting characteristics.

These methods are shared, which means that the methods are not specific to any instance of a String. The String class also contains several other methods that are called based on an instance of a specific String object. The methods on the String class replace the functions that Visual Basic 6.0 had as part of the language for string manipulation, and they perform operations such as inserting strings, splitting strings, and searching strings.

String()

The String class has several different constructors for those situations in which you aren’t simply assigning an existing value to a new string. The term constructor is expanded upon in Chapter 3. Constructors are methods that are used to construct an instance of a class. String() would be the default constructor for the String class, but the String class does not expose this constructor publicly. The following example shows the most common method of creating a String:

  Dim strConstant as String = "ABC" Dim strRepeat as New String("A"c, 20) 

A variable is declared of type string and as a primitive is assigned the value 'ABC'. The second declaration uses one of the parameterized versions of the String constructor. This constructor accepts two parameters: The first is a character and the second is the number of times that character should be repeated in the string.

In addition to creating an instance of a string and then calling methods on your variable, the String class has several shared methods. A shared method refers to a method on a class that does not require an instance of that class. Shared methods are covered in more detail in relation to objects in Chapter 3; for the purpose of this chapter, the point is that you can reference the class string followed by a “.” and see a list of shared methods for that class. For strings, this list includes the following:

Open table as spreadsheet

Shared Methods

Description

Empty

This is actually a property. It can be used when an empty String is required. It can be used for comparison or initialization of a String.

Compare

Compares two objects of type String

CompareOrdinal

Compares two Strings, without considering the local national language or culture

Concat

Concatenates one or more Strings

Copy

Creates a new String with the same value as an instance provided

Equals

Determines whether two Strings have the same value

Equality operator ( =)

An overloaded version of the equality operator that compares two String objects

Inequality operator

( op_Inequality)

A method that accepts two String objects for comparison. The method returns True if the objects are not equal.

IsNullorEmpty

This static method is a very efficient way of determining whether a given variable has been set to the empty string or Nothing.

Not only have creation methods been encapsulated, but other string-specific methods, such as character and substring searching, and case changes, are now available from String objects instances.

The SubString Method

The .NET String class has a method called SubString. Thanks to overloading, covered in Chapter 3, there are two versions of this method: The first accepts a starting position and the number of characters to retrieve, while the second accepts simply the starting location. The following code shows examples of using both of these methods on an instance of a String:

  Dim strMyString as String = "Hello World" Console.WriteLine(strMystring.SubString(0,5)) Console.WriteLine(strMyString.SubString(6)) 

The PadLeft and PadRight Methods

These methods enable you to justify a String so that it is left- or right-justified. As with SubString, the PadLeft and PadRight methods are overloaded. The first version of these methods requires only a maximum length of the String, and then uses spaces to pad the String. The other version requires two parameters: the length of the returned String and the character that should be used to pad the original String. An example of working with the PadLeft method is as follows:

  Dim strMyString as String = "Hello World" Console.WriteLine(strMyString.PadLeft(30)) Console.WriteLine(strMyString.PadLeft(20,"."c)) 

The String Class Is Immutable

The Visual Basic String class isn’t entirely different from the String type that VB programmers have used for years. The majority of string behaviors remain unchanged, and the majority of methods are now available as methods. However, to support the default behavior that people associate with the String primitive type, the String class isn’t declared in the same way as several other classes. Strings in .NET do not allow editing of their data. When a portion of a string is changed or copied, the operating system allocates a new memory location and copies the resulting string to this new location. This ensures that when a string is copied to a second variable, the new variable references its own copy.

To support this behavior in .NET, the String class is defined as an immutable class. This means that each time a change is made to the data associated with a string, a new instance is created, and the original referenced memory is released for garbage collection. This is an expensive operation, but the result is that the String class behaves as people expect a primitive type to behave. Additionally, when a copy of a string is made, the String class forces a new version of the data into the referenced memory. This ensures that each instance of a string references only its own memory. Consider the following code:

  Dim strMyString as String Dim intLoop as Integer For intLoop = 1 to 1000    strMyString = strMyString & "A very long string " Next Console.WriteLine(strMyString) 

This code does not perform well. For each assignment operation on the strMyString variable, the system allocates a new memory buffer based on the size of the new string, and copies both the current value of strMyString and the new text that is to be appended. The system then frees the previous memory that must be reclaimed by the garbage collector. As this loop continues, the new memory allocation requires a larger chunk of memory. The result is that operations such as this can take a long time. However, .NET offers an alternative in the System.Text.StringBuilder object, shown in the following sample code:

  Dim objMyStrBldr as New System.Text.StringBuilder() Dim intLoop as Integer For intLoop = 1 to 1000    ObjMyStrBldr.Append("A very long string ") Next Console.WriteLine(objMyStrBldr.ToString())  

The preceding code works with strings but does not use the String class. The .NET class library contains a class called System.Text.StringBuilder, which performs better when strings will be edited repeatedly. This class does not store strings in the conventional manner; it stores them as individual characters, with code in place to manage the ordering of those characters. Thus, editing or appending more characters does not involve allocating new memory for the entire string. Because the preceding code snippet does not need to reallocate the memory used for the entire string, each time another set of characters is appended it performs significantly faster. Ultimately, an instance of the String class is never explicitly needed because the StringBuilder class implements the ToString method to roll up all of the characters into a string. While the concept of the StringBuilder class isn’t new, the fact that it is now available as part of the Visual Basic implementation means developers no longer need to create their own string memory managers.

String Constants

If you ever have to produce output based on a string you’ll quickly find yourself needing to embed certain constant values. For example, it’s always useful to be able to add a carriage-return linefeed combination to trigger a new line in a message box. One way to do this is to learn the underlying ASCII codes and then embed these control characters directly into your string or string-builder object.

Visual Basic provides an easier solution for working with these: the Microsoft.VisualBasic.Constants class. The Constants class, which you can tell by its namespace is specific to Visual Basic, contains definitions for several standard string values that you might want to embed. The most common, of course, is Constants.VbCrLf, which represents the carriage return linefeed combination. Feel free to explore this class for additional constants that you might need to manipulate string output.

The DBNull Class and IsDBNull() Function

When working with a database, a value for a given column may not be defined. For a reference type this isn’t a problem, as it is possible to set reference types to Nothing. However, for value types, it is necessary to determine whether a given column from the database or other source has an actual value. The first way to manage this task is to leverage the DBNull call and the IsDBNull function. The IsDBNull function accepts an object as its parameter and returns a Boolean that indicates whether the variable has been initialized.

In addition to this method, Visual Basic has access to the DBNull class. This class is part of the System namespace, and to use it you declare a local variable with the DBNull type. This variable is then used with an is comparison operator to determine whether a given variable has been initialized:

  Dim sysNull As System.DBNull = System.DBNull.Value Dim strMyString As String = Nothing If strMyString Is sysNull Then   strMyString = "Initialize my String" End If If Not IsDBNull(strMyString) Then   Console.WriteLine(strMyString) End If 

In this code, the strMyString variable is declared and initialized to Nothing. The first conditional is evaluated to True, and as a result the string is initialized. The second conditional then ensures that the declared variable has been initialized. Because this was accomplished in the preceding code, this condition is also True. In both cases, the sysNull value is used not to verify the type of the object, but to verify that it has not yet been instantiated with a value.

Nullable Types

In addition to having the option to explicitly check for the DBNull value, with Visual Basic 2005 you can create a nullable value type. In the background, when this syntax is used, the system creates a reference type containing the same data that would be used by the value type. Your code can then check the value of the nullable type before attempting to set this into a value type variable. Nullable types are built using generics, discussed in Chapter 7.

For consistency, however, let’s take a look at how nullable types work. The key, of course, is that value types can’t be set to null. This is why nullable types aren’t value types. The following statements shows how to declare a nullable integer:

  Dim intValue as Nullable(Of Integer) 

The intValue variable acts like an integer, but isn’t actually an integer. As noted, the syntax is based on generics, which are covered in Chapter 7, but essentially you have just declared an object of type Nullable and declared that this object will in fact hold integer data. Thus, both of the following assignment statements are valid:

  intValue = 123 intValue = Nothing 

However, at some point you are going to need to pass intValue to a method as a parameter, or set some property on an object that is looking for an object of type Integer. Because intValue is actually of type Nullable, it has the properties of a nullable object. The nullable class has two properties of interest when you want to get the underlying value. The first is the property value. This represents the underlying value type associated with this object. In an ideal scenario you would just use the value property of the nullable object in order to assign to your actual value a type of integer and everything would work. If the intValue.value wasn’t assigned, you would get the same value as if you had just declared a new Integer without assigning it a value.

Unfortunately, that’s not how the nullable type works. If the intValue.value property contains Nothing and you attempt to assign it, then it throws an exception. To avoid getting this exception you always need to check the other property of the nullable type: HasValue. The HasValue property is a Boolean that indicates whether a value exists; if one does not, then you shouldn’t reference the underlying value. The following code is an example of how to safely use a nullable type:

  Dim int as Integer If intValue.HasValue Then  int = intValue.Value End If 

Of course, you could add an Else statement to the preceding and use either Integer.MinValue or Integer.MaxValue as an indicator that the original value was Nothing. The key point here is that nullable types enable you to easily work with nullable columns in your database, but you must still verify whether an actual value or null was returned.

Arrays

It is possible to declare any type as an array of that type. Because an array is a modifier of another type, the basic Array class is never explicitly declared for a variable’s type. The System.Array class that serves as the base for all arrays is defined such that it cannot be created, but must be inherited. As a result, to create an Integer array, a set of parentheses is added to the declaration of the variable. These parentheses indicate that the system should create an array of the type specified. The parentheses used in the declaration may be empty or may contain the size of the array. An array can be defined as having a single dimension using a single number, or as having multiple dimensions.

All .NET arrays at an index of zero have a defined number of elements. However, the way an array is declared in Visual Basic varies slightly from other .NET languages such as C#. When the new .NET version of Visual Basic was announced, it was also announced that arrays would always begin at 0 and that they would be defined based on the number of elements in the array. In other words, Visual Basic would work the same way as the other initial .NET languages. However, in older versions of Visual Basic, it is possible to specify that an array should start at 1 instead of 0. This meant that a lot of existing code didn’t define arrays based on their upper limit. To resolve this issue, the engineers at Microsoft decided on a compromise: All arrays in .NET begin at 0, but when an array is declared in Visual Basic, the definition is based on the upper limit of the array, not the number of elements.

The main result of this upper-limit declaration is that arrays defined in Visual Basic have one more entry by definition than those defined with other .NET languages. Note that it’s still possible to declare an array in Visual Basic and reference it in C# or another .NET language. The following code illustrates some simple examples to demonstrate five different ways of creating arrays, using a simple integer array as the basis for the comparison:

  Dim arrMyIntArray1(20) as Integer Dim arrMyIntArray2() as Integer = {1, 2, 3, 4} Dim arrMyIntArray3(4,2) as Integer Dim arrMyIntArray4( , ) as Integer = _     { {1, 2, 3},{4, 5, 6}, {7, 8, 9},{10, 11, 12},{13, 14 , 15} } Dim arrMyIntArray5() as Integer 

In the first case, the code defines an array of integers that spans from arrMyIntArray1(0) to arrMyIntArray1(20). This is a 21-element array, because all arrays start at 0 and end with the value defined in the declaration as the upper bound. The second statement creates an array with four elements numbered 0 through 3, containing the values 1 to 4. The third statement creates a multidimensional array containing five elements at the first level, with each of those elements containing three child elements. The challenge is to remember that all subscripts go from 0 to the upper bound, meaning that each array contains one more element than its upper bound. The result is an array with 15 elements. The next line of code, the fourth, shows an alternative way of creating the same array, but in this case there are four elements, each containing four elements, with subscripts from 0 to 3 at each level. Finally, the last line demonstrates that it is possible to simply declare a variable and indicate that the variable is an array, without specifying the number of elements in that array.

Multidimensional Arrays

As shown earlier in the sample array declarations, the definition of arrMyIntArray3 is a multi dimensional array. This declaration creates an array with 15 elements (five in the first range, each containing three elements) ranging from arrMyIntArray3(0,0) through arrMyIntArray3(2,1) to arrMyIntArray3(4,2). As with all elements of an array, when it is created without specific values, the value of each of these elements is created with the default value for that type. This case also demonstrates that the size of the different dimensions can vary. It is possible to nest deeper than two levels, but this should be done with care because such code is difficult to maintain.

The fourth declaration shown previously creates arrMyIntArray4(, ) with predefined values. The values are mapped based on the outer set being the first dimension and the inner values being associated with the next inner dimension. For example, the value of arrMyIntArray4(0,1) is 2, while the value of arrMyIntArray4(2,3) is 12. The following code snippet illustrates this using a set of nested loops to traverse the array. Additionally, it provides an example of calling the UBound method with a second parameter to specify that you are interested in the upper bound for the second dimension of the array:

  Dim intLoop1 as Integer Dim intLoop2 as Integer For intLoop1 = 0 to UBound(arrMyIntArray4)   For intLoop2 = 0 to UBound(arrMyIntArray4, 2)     Console.WriteLine arrMyIntArray4(intLoop1, intLoop2).ToString   Next Next 

The UBound Function

Continuing to reference the arrays defined earlier, the declaration of arrMyIntArray2 actually defined an array that spans from arrMyIntArray2(0) to arrMyIntArray2(3). This is the case because when you declare an array by specifying the set of values, it still starts at 0. However, in this case you are not specifying the upper bound, but rather initializing the array with a set of values. If this set of values came from a database or other source, then it might not be clear what the upper limit on the array was. To verify the upper bound of an array, a call can be made to the UBound function:

  Console.Writeline CStr(UBound(ArrMyIntArray2)) 

The preceding line of code retrieves the upper bound of the first dimension of the array. However, as noted in the preceding section, it is possible to specify an array with several different dimensions. Thus, this old-style method of retrieving the error carries the potential for an error of omission. The better way to retrieve the upper bound is to use the GetUpperBound method. In this case, you need to tell the array which upper-bound value you want, as shown here:

  ArrMyIntArray2.GetUpperBound(0) 

This is the preferred method of getting an array’s upper bound because it explicitly indicates which upper bound is wanted when using multidimensional arrays.

The UBound function has a companion called LBound. The LBound function computes the lower bound for a given array. However, as all arrays and collections in Visual Basic are 0-based, it doesn’t have much value anymore.

The ReDim Statement

The final declaration demonstrated previously is for arrMyIntArray5(). This is an example of an array that has not yet been instantiated. If an attempt were made to assign a value to this array, it would trigger an exception. The solution to this is to use the ReDim keyword. Although ReDim was part of Visual Basic 6.0, it has changed slightly. The first change is that code must first Dim an instance of the variable; it is not acceptable to declare an array using the ReDim statement. The second change is that code cannot change the number of dimensions in an array. For example, an array with three dimensions cannot grow to an array of four dimensions, nor can it be reduced to only two dimensions. To further extend the example code associated with arrays, consider the following, which manipulates some of the arrays previously declared. Note that the arrMyIntArray5 declaration was repeated for this example because this variable isn’t actually usable until after it is re-dimensioned in the following code:

 Dim arrMyIntArray5() as Integer ' The commented statement below would compile but would cause a runtime exception. 'arrMyIntArray5(0) = 1 ReDim arrMyIntArray5(2) ReDim arrMyIntArray3(5,4) ReDim Preserve arrMyIntArray4(UBound(arrMyIntArray4),2) 

The ReDim of arrMyIntArray5 instantiates the elements of the array so that values can be assigned to each element. The second statement redimensions the arrMyIntArray3 variable defined earlier. Note that it is changing the size of both the first and the second dimension. While it is not possible to change the number of dimensions in an array, it is possible to resize any of an array’s dimensions. This capability is required if declarations such as Dim arrMyIntArray6( , , ,) As Integer are to be legal.

By the way, while it is possible to repeatedly ReDim a variable, this is the type of action that should ideally be done only rarely, and never within a loop. If you intend to loop through a set of entries and add entries to an array, try to determine the number of entries you’ll need before entering the loop, or at a minimum ReDim the size of your array in chunks to improve performance.

The Preserve Keyword

The last item in the code snippet in the preceding section illustrates an additional keyword associated with redimensioning. The Preserve keyword indicates that the data stored in the array prior to redimensioning should be transferred to the newly created array. If this keyword is not used, then the data stored in an array is lost. Additionally, in the preceding example, the ReDim statement actually reduces the second dimension of the array. While this is a perfectly legal statement, note that this means that even though you have asked to preserve the data, the data values 4, 8, 12, and 16 that were assigned in the original definition of this array will be discarded. These are lost because they were assigned in the highest index of the second array. Because arrMyIntArray4(1,3) is no longer valid, the value that resided at this location has been lost.

Arrays continue to be very powerful in Visual Basic, but the basic Array class is just that, basic. While it provides a powerful framework, it does not provide a lot of other features that would allow for more robust logic to be built into the array. To achieve more advanced features, such as sorting and dynamic allocation, the base Array class has been inherited by the classes that make up the Collections namespace.

Collections

The Collections namespace is part of the System namespace and provides a series of classes that implement advanced array features. While the capability to make an array of existing types is powerful, sometimes more power is needed in the array itself. The ability to inherently sort or dynamically add dissimilar objects in an array is provided by the classes of the Collections namespace. This namespace contains a specialized set of objects that can be instantiated for additional features when working with a collection of similar objects. The following table defines several of the objects that are available as part of the System.Collections namespace:

Open table as spreadsheet

Class

Description

ArrayList

Implements an array whose size increases automatically as elements are added

BitArray

Manages an array of Booleans that are stored as bit values

Hashtable

Implements a collection of values organized by key. Sorting is done based on a hash of the key

Queue

Implements a first in, first out collection

SortedList

Implements a collection of values with associated keys. The values are sorted by key and are accessible by key or index.

Stack

Implements a last in, first out collection

Each of the objects listed is focused on storing a collection of objects. This means that in addition to the special capabilities each provides, it also provides one additional capability not available to objects created based on the Array class. In short, because every variable in .NET is based on the Object class, it is possible to have a collection defined, because one of these objects contains elements that are defined with different types. This is true because each of these collection types stores an array of objects, and since all classes are of type Object, a string could be stored alongside an integer value. The result is that it’s possible within the collection classes for the actual objects being stored to be different. Consider the following example code:

  Dim objMyArrList As New System.Collections.ArrayList() Dim objItem As Object Dim intLine As Integer = 1 Dim strHello As String = "Hello" Dim objWorld As New System.Text.StringBuilder("World") ' Add an integer value to the array list. objMyArrList.Add(intLine) ' Add an instance of a string object objMyArrList.Add(strHello) ' Add a single character cast as a character. objMyArrList.Add(" "c) ' Add an object that isn't a primitive type. objMyArrList.Add(objWorld) ' To balance the string, insert a break between the line ' and the string "Hello", by inserting a string constant. objMyArrList.Insert(1, ". ") For Each objItem In objMyArrList   ' Output the values...   Console.Write(objItem.ToString()) Next 

The preceding code is an example of implementing the new ArrayList collection class. The collection classes, as this example shows, are versatile. The preceding code creates a new instance of an ArrayList, along with some related variables to support the demonstration. The code then shows four different types of variables being inserted into the same ArrayList. Next, the code inserts another value into the middle of the list. At no time has the size of the array been declared, nor has a redefinition of the array size been required.

Part of the reason for this is that the Add and Insert methods on the ArrayList class are defined to accept a parameter of type Object. This means that the ArrayList object can literally accept any value in .NET. This comes at a slight performance cost for those variables that are value types because of boxing.

Specialized and Generic Collections

Visual Basic has additional classes available as part of the System.Collections.Specialized name-space. These classes tend to be oriented around a specific problem. For example, the ListDictionary class is designed to take advantage of the fact that while a hash table is very good at storing and retrieving a large number of items, it can be costly when there are only a few items. Similarly, the StringCollection and StringDictionaryclasses are defined so that when working with strings, the time spent interpreting the type of object is reduced and overall performance is improved. Each class defined in this namespace represents a specialized implementation that has been optimized for handling specific datatypes.

This specialization is different from the specialization provided by one of Visual Studio 2005’s new features, generics. The System.Collections.Generics namespace contains versions of the collection classes that have been defined to support generics. The basic idea of generics is that because there are performance cost and reliability concerns associated with casting to and from the object type, collections should allow you to specify what specific type they will contain. Generics not only prevent you from paying the cost of boxing for value types but, more important, add to the capability to create type-safe code at compile time. Generics are a powerful extension to the .NET environment and are covered in detail in Chapter 7.




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