Data Type Features

Data Type Features

Visual Basic .NET has a number of features that simplify programming, including strong typing, type safety, and data widening, but the best new feature is probably the System.Object class, from which all other classes are derived.

The System.Object Class

Almost all modern programming languages have some sort of run-time library that provides common services and ways to access the underlying hardware, operating system, and file storage. In Chapter 2, you learned that the common language runtime is organized into a single hierarchical tree of namespaces. At the root of this tree is the System namespace, which contains various objects, including predefined types such as integers, strings, and classes. (The System.Windows.Forms namespace we used in Chapter 2 is inherited from the System namespace.)

All of the common predefined types can be used from any language the .NET Framework supports. All of the System classes are contained in Mscorlib.dll and can be used by all .NET applications. You can use these classes as is or derive your own classes from them.

At the root of all classes (either inherited classes or those we write ourselves) is the System.Object class. In Visual Basic .NET, everything implicitly derives from System.Object. The System.Object class is the ultimate super class of all classes in the .NET Framework—it's the only class in the .NET Framework that does not inherit from any other object type. All other object types must either explicitly or implicitly declare support for (inherit from) exactly one other object type. In fact, in Visual Basic .NET it's impossible to use a class that does not inherit from System.Object. This fact ensures that every object in Visual Basic .NET inherits System.Object's basic functionality.

All methods defined in the System.Object class are available in all of its subclasses, which really means all objects in the system. The System.Object class (and therefore every class) has the six basic methods listed in Table 4-3.

Table 4-3  Basic Visual Basic .NET Class Methods

Object.Method

Access

Description

Equals

Public

Takes another object as a parameter and returns a Boolean True or False that indicates whether the two objects are equal.

GetHashCode

Public

Returns an integer hash code that represents the object's value. This code is usually used as a key when the object is added to a collection. Two identical objects should generate the same code.

Finalize

Protected

The CLR calls an object's Finalize method to notify the object that the object is about to be destroyed. This method really does nothing and is overridden by the class.

MemberwiseClone

Protected

Creates a shallow copy of the object.

GetType

Public

Returns an instance of System.Type, which is used to get information about the object through metadata.

ToString

Public

Returns a string representation of the object. The string is not formatted and is almost always overridden by the class implementing it.

Examine Figure 4-3, and notice that neither the Finalize method nor the MemberwiseClone method is present in the IntelliSense list. (The Equals, GetHashCode, and ToString methods are available but are not in the list.) The methods are absent because they are defined with the Protected access modifier in the System.Object class; only methods defined with the Public access modifier are listed. Remember that methods defined as Protected can be accessed only from a child class. When you inherit a class from the System.Object class, you will see all five methods.

Figure 4-3

The IntelliSense list displays Public methods only.

Now let's make use of those methods by extending the code shown in Figure 4-3 as follows:

Imports System Public Module Module1               Dim myObject1 As New System.Object()     Dim myObject2 As New System.Object()     Dim bReslt As Boolean     Public Sub main()                  myObject1 = "Hello Visual Basic .NET"         myObject2 = 42                  'What type is this?         MessageBox.Show(myObject1.GetType.ToString)   'System.String         'What is the object's HashCode?         MessageBox.Show(myObject1.GetHashCode.ToString)    '-1757321832         'Are the objects equal?         MessageBox.Show(myObject1.Equals(myObject2).ToString)  'False          myObject1 = myObject2         MessageBox.Show(myObject1.Equals(myObject2).ToString)  'True         MessageBox.Show(myObject1.ToString) '42     End Sub  End Module

note

You must explicitly convert each message box argument to a string. This requirement is an example of the strong typing in Visual Basic .NET, which I'll cover in greater detail later in this chapter.

The GetType method of myObject1 in the first message box prints System.Object. The GetHashCode method prints -1757321832 on my system, but your result might be different. In a real program, we would override the GetHashCode method in a derived class to return some unique number that could be used as, say, a key in a collection.

Next, the Equals method of myObject1 compares myObject1 to myObject2. False is returned, of course, because myObject1 and myObject2 are not only different individual objects but the objects themselves contain different values. Because these variables are of type System.Object, they readily accept strings, numbers, or any other data type without complaining.

The next line of code sets myObject1 equal to myObject2 so that both objects now refer to myObject2. The last line prints out the value of myObject1. This final message box indicates that both variables reference the same object—a memory location that holds the value 42.

As I mentioned earlier, the Object type replaces the Variant data type found in previous versions of Visual Basic and can contain anything; for example, a string, a double, or a reference to a Microsoft Word document. Although you'll almost always want to dimension your reference variables as a specific data type, you'll need to dimension a variable as type Object in some cases. These cases should be few and far between, however. Why? Because even though a variable declared as type Object is flexible enough to contain a reference to any object or data type, calling a method using an Object type variable forces late (run-time) binding, which essentially means that each time a program accesses an Object variable, the CLR has to determine the variable's type. Not only does this interrogation take CPU cycles, but loose-typed variables can lead to bugs. For example, you might accidentally pass an Object that holds a string to a method that's expecting an integer. Mistakes such as these will become apparent only at run time and can cause all sorts of embarrassment when your users get a run-time error.

Because Visual Basic .NET uses strong typing, you should force early (compile-time) binding by dimensioning the variable as a specific object type. And if you can't dimension it as a specific type, you should cast (convert) it to a specific data type wherever possible.

Strong Typing

Strong typing requires you to specify a specific data type for each variable in your program. Strong-typed variables allow IntelliSense to display a variable's properties and methods as you work in the editor. Strong typing also permits the compiler to perform type checking, which ensures that statements that use an improper data type are caught before they can cause subtle run-time errors. Finally, strong typing results in faster execution of your code because the compiler doesn't have to waste CPU cycles determining a variable's type and then performing behind-the-scenes type conversions. Requiring strong typing is one way that .NET forces you to weed out bugs from your code.

In previous versions of Visual Basic, the compiler provided the primitive data types. Recall from Chapter 1, "Visual Basic .NET from the Ground Up," that the primitive data types for all .NET languages are provided by the CLR. The .NET language compilers make a subset of all possible data types available to the various language implementations. The .NET language-specific subset of data types is mapped to the System.Type class so that each .NET data type is the same regardless of which language you use—it's guaranteed that the code written in one .NET language can interact seamlessly with code from another .NET language. For those of you that used API function declarations in previous versions of Visual Basic, not having to fiddle with casting data types to the expected C language format will be a welcome relief. The standard .NET data types also facilitate portability so that programs can run (without recompiling) on any operating system that supports the common language runtime.

Type Safety

Visual Basic .NET is a type-safe language. You now know that you can access a variable only through the type associated with that variable—the compiler will bark if you do otherwise because it won't let you mismatch types. This restriction encourages (a nice way to say forces) good program design. It also eliminates potential bugs or security breaches by making the accidental or malicious overwriting of one variable by another impossible. However, if you do not specify a data type when you declare a variable, Visual Basic .NET assigns the Object data type to that variable. This default designation is similar to the way Visual Basic 6 assigns the Variant data type to variables that you don't specify a data type for.

Visual Basic 6 has the Option Explicit directive that forces all variables to be declared. Visual Basic .NET supports this directive and the new Option Strict directive, as shown in Table 4-4.

Table 4-4  Visual Basic .NET Type Safety Directives

Directive

Values

Description

Option Explicit

On | Off

Used to force explicit declaration of all variables in a module. This directive is on by default.

Option Strict

On | Off

Restricts implicit data type conversions to widening conversions. This explicitly disallows any data type conversions in which data loss would occur. It also disallows any conversion between numeric types and strings. This directive is off by default.

When the Option Strict directive is set to On, all variables must have an AS clause that explicitly declares the variable's type. Also, the & operator can't be used to concatenate Object variables. It's good practice to always use the Option Explicit directive in each module to ensure that each variable is explicitly declared using the Dim, the Private, the Public, or the ReDim statement—the compiler generates an error for each undeclared variable when Option Explicit is turned on. If Option Explicit and Option Strict are both turned off, you can use an undeclared variable—which would default to type Object—as shown here:

Option Explicit Off Option Strict Off myVariable = "Hello Visual Basic .NET"

Apart from the fact that undeclared variables can introduce subtle bugs into your programs, reading the code becomes a challenge because keeping track of undeclared variables is very difficult. Unless you have a really good reason—and I can't think of one off the top of my head—always keep both of these directives turned on. Think of them as free insurance policies.

Here's another example of what happens if you don't declare a data type. If Option Strict is off, the following code works fine.

Dim myVariable = "Hello Visual Basic .NET"   MessageBox.Show(myVariable & " is type " & _     myVariable.GetType.ToString())

Notice that the Dim statement that declares myVariable does not include an AS clause, so myVariable defaults to type Object, which can hold any data type. This message box will display the contents of the variable as well as the data type, as shown in Figure 4-4.

Figure 4-4

In this figure, myVariable holds a string.

We could assign a double to the same object variable that just held a string.

myVariable = 123.456 MessageBox.Show(myVariable & " is type " & _     myVariable.GetType.ToString())

The message box shown in Figure 4-5 reveals that myVariable now holds a double.

Figure 4-5

In this figure, myVariable holds a double.

You might be scratching your head right about now, wondering why this code is legal if Visual Basic .NET is type safe. The answer is that it's not legal unless you specifically turn off the Option Explicit directive.

You can still generate the same type of bugs as you can in Visual Basic 6 by turning Option Explicit off. For example, you might declare oMyObject but later in the code use a variable named MyObject. Consider this code fragment:

Sub Main()     Dim oMyObject As Object     oMyObject = "Hello Visual Basic .NET!"     MessageBox.Show(MyObject) End Sub

The programmer probably meant to type oMyObject in the message box. (It's easy to mistype a variable name.) When he runs the program he expects to see "Hello Visual Basic .NET!" when the message box appears. Because MyObject does not exist, Visual Basic .NET creates it and the message box is empty. Keeping both Option Strict and Option Explicit on (the default) avoids this problem and is the safe way to go.

Testing for Variable Type

As I've mentioned, Visual Basic .NET gives every data type a default value when the variable is dimensioned. Let's assume a program dimensions an Object variable and an Integer variable as shown here:

Dim myObject1 As New System.Object() Dim myInteger1 As New Integer() MessageBox.Show(myObject1.ToString)    'System.Object MessageBox.Show(myInteger1.ToString)   '0

An Object variable's default implementation of the ToString method returns the fully qualified name of the object's class. Notice that the variable's value is not printed, as it is with the Integer variable. Although you'll usually override the ToString methods of your objects, the built-in methods are good for debugging purposes.

Value type variables inherit from the Object.ValueType class. The Object.ValueType class overrides the object's ToString method to display the value of the variable. This fact is illustrated by the previous example, which prints the integer variable's value—0—instead of the variable's type.

We can test the type of object by using the built-in function TypeName, as shown here:

MessageBox.Show(TypeName(myObject1))  'Object MessageBox.Show(TypeName(myInteger1)) 'Integer

A Typical Visual Basic .NET Assignment

Let's consider another example that clearly explains the type system. I know you'll soon run into the challenge of trying to figure out what data type the CLR is looking for in an assignment. For example, you might add a text box control named txtName to a form created from Windows.Forms.Form to display an employee's name. You might decide to change the text box background color to yellow when the user is editing. Your code might look something like this:

txtName.BackColor = "yellow"

Unfortunately, the Visual Basic .NET editor knows the code is incorrect and is not shy about telling you so, as shown in Figure 4-6. The problem is that the BackColor method of the txtName text box is expecting a type System.Drawing.Color, and you had the audacity to pass it a string.

Figure 4-6

The Visual Basic .NET editor warns about data type mismatches.

Take a moment and think about what you're trying to do. Of course, you're trying to set the BackColor property of the text box. You can see from the IDE message that the BackColor property is expecting a System.Drawing.Color data type, not a string. The Visual Basic .NET compiler can't explicitly coerce a string to a data type of System.Drawing.Color, nor would we want it to try.

Every data type in the .NET Framework exposes a set of properties and methods. You can solve the problem in this example by using these properties and methods to convert your string to the System.Drawing.Color data type that the BackColor property expects. The following code does the job nicely. The Color class provides a method to do the explicit conversion for us. When you type in System.Drawing.Color, the IDE presents you with a rainbow of color choices. We simply select Yellow.

txtName.BackColor = System.Drawing.Color.Yellow

So you can see that the System.Drawing.Color class has various color constants, such as Yellow, that we can set. In addition to the color property, the System.Drawing.Color class has the useful method FromName. If you know the color constants that are predefined, you can use the FromName method and pass in a string with the color you want to display.

txtName.BackColor = System.Drawing.Color.FromName("blue")

These color assignment examples illustrate a typical assignment problem that beginners in Visual Basic .NET will encounter frequently. If you are unfamiliar with how to solve this problem, as we've done here, it can be frustrating. While our problem can be solved in two ways, you are probably thinking that this code looks more like Klingon than Visual Basic. And it also might have crossed your mind that you have no idea where to find all of these classes, let alone understand how they fit together.

Well, don't worry. In the next chapter, we'll examine the .NET Framework in detail and you'll learn how to quickly find what you need. And remember, to help us out the language designers provided the WinCV tool, which we saw in earlier chapters, that presents a hierarchical view that makes finding any class, method, or procedure a snap.

Data Widening

Earlier I mentioned that the Option Strict directive restricts implicit data type conversions to widening conversions. This restriction explicitly disallows any data type conversions in which data loss would occur and also disallows any conversion between numeric types and strings.

A widening conversion changes the value to a type that can accommodate data of the same or greater magnitude. Table 4-5 shows the standard data type widening conversions.

Table 4-5  Data Widening Conversions

Data Type

Widens to Data Types

Byte

Byte, Short, Integer, Long, Decimal, Single, Double

Short

Short, Integer, Long, Decimal, Single, Double

Integer

Integer, Long, Decimal, Single, Double

Long

Long, Decimal, Single, Double

Decimal

Decimal, Single, Double

Single

Single, Double

Double

Double

Char

String

Any type

Object

Conversions from Integer to Single, or from Long or Decimal to Single or Double, might result in a loss of precision but never in a loss of magnitude. In this sense, they do not incur information loss because you are storing a value in a larger area. However, the reverse is not the case. Consider this code:

Dim iInteger As Integer = 10 Dim lLong As Long          lLong = iInteger    'This works fine; we can safely convert                     ' an iInteger into a lLong. iInteger = lLong    'This does not work; we can't safely convert                     ' a lLong into an iInteger.

When this program is run, it generates an error message that states that Option Strict disallows implicit conversions from Long to Integer. Option Strict is again helping us stay away from subtle bugs. Of course, turning off Option Strict permits implicit conversions, but you should always stay away from doing this. It's easy to accidentally try to fit a number larger than can fit in an Integer into a variable named iInteger. And because this error happens at run time, you might never encounter the error until your program is deployed in the field. Option Strict ensures these overflow bugs can't happen in Visual Basic .NET

Any conversion that does not result in a loss of precision (a narrowing conversion) will not throw an exception. (An exception is a type of error. I'll cover exceptions in greater detail in Chapter 7, "Handling Errors and Debugging Programs.") You can still assign the contents of a Long variable to an Integer variable by explicitly casting the Long to an Integer. Of course, the compiler will assume you know what you are doing. Casting an expression means you are going to coerce an expression to a given type. In some programming circles, casting is known as "evil type coercion." Specific cast keywords are used to coerce expressions into the primitive types. The general cast keyword, CType, coerces an expression into any type. If no conversion exists from the type of the expression to the specified type, a compile-time error occurs. Otherwise, the result is the value produced by the conversion.

CastExpression ::= CType ( Expression , TypeName ) |   CastTarget ( Expression ) CastTarget ::=  CBool | CByte | CChar | CDate | CDec |     CDbl | CInt | CLng | CObj | CShort | CSng | CStr

We could cast the contents of our Long into an Integer by doing the following:

iInteger = CType(lLong, Integer)

But here's the problem with doing this. Let's say the value of the Long is one more than the maximum value that can be held by an Integer. As our program runs, it calculates values and unexpectedly holds a value larger than we think. Then we try to cast the value of the Long into an Integer, as shown here:

Dim iInteger As Integer  Dim lLong As Long = 2147483648 'One more than the maximum                                ' integer value (2,147,483,647) iInteger = CType(lLong, Integer)

Oops! We get a run-time error. You can't predict this error because a Long can always hold a larger value than an Integer. The only time this error will show up is during run time, as you can see in Figure 4-7.

Figure 4-7

There is no way to predict this error.

The Integer variable is not explicitly initialized, so the compiler assigns it the default value 0. The code then initializes the Long variable lLong to a value one greater than the integer can hold. The CLR realizes the value is too large for iInteger and throws the exception shown in Figure 4-7.

A run-time error such as this is the worst type of error. A design-time error is easy to fix—in fact, we can't even compile and run the program until we fix it—but a run-time error is much more insidious. The erroneous code might be buried in a deep dark section of your program that rarely gets executed and might have been missed during testing. Or, depending on the sequence of events, a variable might be assigned a large value in an unforeseen way. So you see, the strong typing nature of Visual Basic .NET is really there for your benefit. It forces you to assign one data type to a like data type, or you have to explicitly cast it and accept responsibility for ensuring that no data loss occurs.

The moral of this story is to never shut off the Option Strict directive or the Option Explicit directive and be sure not to convert a large variable type into a smaller type unless you have an exceptional reason for doing so. And if you have an exceptional reason, you must be sure that the converted value will never exceed the storage capability of the smaller type.

It might seem surprising that a conversion from a derived (inherited) type to one of its base types is considered widening. This convention exists because the derived type contains all the members of the base type, which means the conversion will fully populate the base type. The base type, however, does not have all the members of the derived type, so converting from a base type to a derived type does not fully populate the derived type—it's considered narrowing.

In your programs, you can safely use widening conversions. Widening conversions will always succeed and can always be performed implicitly. However, a narrowing conversion can cause information loss. As we saw, narrowing conversions do not always succeed and might very well fail at run time. That's why the compiler does not allow implicit narrowing—you must explicitly perform this type of conversion.



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

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