Chapter 6. Data and Data Types

Data is a funny word—although not as funny as datum. Our minds are filled with data: the useful and useless trivia that clogs thought; the millions of memories that keep superficial conversations going strong. But the word data rarely comes up in conversation. Unless you are a computer junkie, or you hang around the office all hours of the day or night waiting for reports of crunched numbers, you never have a need to use the term. I have never been asked to lend someone a cup of data. My friends never try to judge my health by asking, “How’s your data going?” And you almost never hear it used as a character name in popular science fiction television shows.

Despite its lack of usage in everyday communication, data is extremely important. In the programming world, it is everything. In this chapter, we will discuss how Visual Basic uses and manipulates data within your applications, and how you can master the tools that make this manipulation possible.

The Nature of Computer Data

In Chapter 2, I mentioned how all data in a computer eventually breaks down to individual bits, electrical impulses that represent either 1 or 0, on or off, true or false. Since our decimal number system requires more than just those two values, computers work in the world of binary—a number system limited to only the numbers 0 and 1. Fortunately, it’s pretty easy to represent basic decimal integer numbers using binary notation. You probably remember Mrs. Green back in second grade telling you about the different place values of multidigit numbers, shown in Figure 6-1.

The fruits of Mrs. Green’s labors

Figure 6-1. The fruits of Mrs. Green’s labors

The same type of diagram can be used for binary numbers; only the position names and values are changed. For convenience, we call these positions by their decimal names, or use the related powers of two. All of this is shown in Figure 6-2.

The positions of an “8-bit” (8-digit) binary number

Figure 6-2. The positions of an “8-bit” (8-digit) binary number

To figure out what this number is in decimal, just add up the columns. Let’s see, there’s one each of fours, eights, and sixty-fours, and none of the rest; 4 + 8 + 64, that’s 76. Since any binary digit can never be more than 1, the counting is pretty simple. I showed an 8-bit (8-digit) binary example here—which can handle the numbers 0 through 255—but you can represent larger decimal numbers by adding more binary digits.

That’s just fine for integer values, but how do you represent decimal and fractional numbers? What about negative numbers; where do they fit in this binary system? And it’s not just numbers. My computer can process text data, arrays of numbers, graphical images, and customer records. How are those stored in binary form?

To handle myriad data forms, every computer includes a small community of Lilliputians who are good at math, language, and art. No wait, I think that’s from a story I’m reading my son at bedtime. Oh yes, now I remember. Computers implement data types to handle all the various forms of data to be managed. Each data type acts as an interpreter between a collection of bits and a piece of information that a computer user can better utilize and understand.

All data types ultimately store their content as individual bits of data, but they differ in how those bits get interpreted. Imagine a data type named Vitamin that indicated which vitamins were included in a food product. Figure 6-3 shows how the 8 bits used earlier could be assigned and interpreted as vitamins.

Loaded with vitamins B6, D, and E

Figure 6-3. Loaded with vitamins B6, D, and E

With such a data type, you could assign vitamin values to food items tracked in your application. (This is just a sampling of vitamins; you would require more bits to handle all of the vitamins. This example should not be construed as an offer of medical services. Consult your doctor.)

For an example that is more in tune with Visual Basic, take that number 76 we were discussing earlier. It’s easy enough to convert it to binary representation, as in 01001100. The .NET Framework includes a few data types that do this conversion automatically, varying only by the number of binary digits (bits) they can handle. In the computer world, 76 also represents a letter of the alphabet—the capital letter L. That’s because there’s a data type that establishes a dictionary between binary values and alphabetic (and other) characters. Windows programs have long used ASCII (American Standard Code for Information Interchange) as its number-to-character dictionary. This 8-bit system documents how to convert the numbers 0 through 255 into all the various characters used in English, including punctuation and other miscellaneous characters. Another dictionary, Unicode, uses 16 bits of data to handle around 65,000 different characters. .NET uses Unicode for its character and “string” data types.

Another rule-bearing data type is Boolean, which uses a single bit to represent either True (a bit value of 1) or False (0). Negative integers, floating-point and fixed-point decimal values, and dates and times round out the kinds of basic data most often managed by computers and their applications. More complex data structures can be built up from these basic types.

Data in .NET

All data types in .NET are implemented as classes within the System namespace. One such data type is System.Byte, which implements an 8-bit integer value, just like we discussed earlier. It holds integer values from 0 to 255. These values are always stored using 8 bits of binary data, but they magically appear in decimal form whenever you ask them to be presented.

The .NET Framework includes 15 core interpretive data types: 8 for integers, 3 for decimal numbers, 2 for character data, a combined data type for dates and times, and a Boolean data type.

Integer Data Types

Based on the number of available data types (8 out of the 15 core types), you would think that most programmers worked with integers all day long—and you’d be right. Whether it’s actual user data or loop counters or status codes or the storage method for enumerated data types, integers show up everywhere in .NET code.

The range of values for an integer data type depends directly on the number of binary digits managed by that data type; the more digits, the bigger the range. Also, half of the integer data types store both positive and negative values (called “signed” integers), whereas the other half support only positive numbers (“unsigned”). Table 6-1 lists the eight integer data types included with .NET, and their associated ranges.

Table 6-1. Integer data types in .NET

.NET data type

Bits

Style

Range of values

System.Byte

8

Unsigned

0 to 255

System.SByte

8

Signed

-128 to 127

System.Int16

16

Signed

-32,768 to 32,767

System.UInt16

16

Unsigned

0 to 65,535

System.Int32

32

Signed

-2,147,483,648 to 2,147,483,647

System.UInt32

32

Unsigned

0 to 4,294,967,295

System.Int64

64

Signed

-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

System.UInt64

64

Unsigned

0 to 18,446,744,073,709,551,615

Looking at these types another way, Table 6-2 shows the relationship between the types and their number of bits and range style.

Table 6-2. Bits and signed status for integer .NET data types

 

8-bits

16-bits

32-bits

64-bits

Signed

SByte

Int16

Int32

Int64

Unsigned

Byte

UInt16

UInt32

UInt64

Decimal Data Types

Once upon a time, life was happy. Strangers said hello when they met you on the street. Succulent fruit burst forth from the trees. In short, God was in His heaven, and everything was right with the world—and then along came fractions. At first, they didn’t seem that bad, since so many of them could be easily converted into a plain numeric form by inserting a decimal point in the number: ½ became 0.5; ¼ became the longer yet smaller 0.25; ⅓ became 0.33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 . . . hey, what’s going on here? I can’t write all those 3s. The book would be 2,000 pages, or more. Eventually people discovered that in many cases, it just wasn’t worth the bother of writing out all the 3s, so they just stopped at some point, as in 0.33333333. It wasn’t perfectly accurate, but it was good enough.

This is what life is like for computer-based decimal values. You can have perfect accuracy—up to a point. After that, you have to settle for good enough. The .NET Framework includes three decimal data types. Two of them accept limited accuracy in exchange for a large range of values. The third has perfect accuracy, but its range is more limited. Table 6-3 documents these three types.

Table 6-3. An accurate list of the inaccurate decimal data types

.NET data type

Accuracy

Range

Description

System.Decimal

Perfect

Limited

The Decimal data type provides around 28 combined digits on both sides of the decimal point. And although it may truncate after the last available digit position, it is accurate within those digits. Because of this, it is perfect for working with money.

The more digits you have on the left of the decimal, the fewer you have available for the right of the decimal, and vice versa. For numbers with no decimal portion, the range is from −79,228,162,514,264,337,593,543,950,335 to 79,228,162,514,264,337,593,543,950,335. (That’s 29 digits, but who’s counting?) For numbers with only zero (0) to the left of the decimal, the range is −0.0000000000000000000000000001 to 0.0000000000000000000000000001.

System.Single

Imperfect

Big

The Single data type offers a much larger range than Decimal does, but it does have some accuracy problems. Sometimes when you do a complex calculation that you know should result in zero, the actual calculated result might be 0.0000000000023. It’s close to zero, but not exactly zero. But you can use very large or very small numbers. For negative values, the range is −3.402823E+38 to −1.401298E−45; for positive values, its range is 1.401298E−45 to 3.402823E+38.

System.Double

Imperfect

Huge

The Double data type is just like the Single data type, but with a bigger attitude—I mean a larger range. For negative values, the range is −1.79769313486231E+308 to −4.94065645841247E−324; for positive values, the range is 4.94065645841247E−324 to 1.79769313486232E+308.

Character Data Types

Hey, check this out. ktuefghbiokh. Pretty cool, eh? That’s the power of a computer in action managing text data. So efficient; so graceful; so lskjdfljsdfjl. Although computers are really number machines, they handle text just as well. Of course, it’s really just you doing all the wordsmithing. In fact, the computer isn’t even smart enough to tell the difference between numbers and letters; it’s all bits to the CPU. Pretty mindless, if you ask me. I mean, what’s the use of having all that computing power if you can’t even think?

Despite all their speed and technology, computers are still just lumps of silicon wrapped up in a nice package. The computer I’m typing on doesn’t even know that I’m insulting it; I can type these things on and on, and there’s nutten that thiz komputre cann due about itt.

The framework includes two text-related data types: System.Char and System.String. The Char data type holds a single character, no more, no less. At 16 bits, it holds any of the thousands of Unicode characters.

The String data type allows up to about two billion Unicode characters to be “strung” together into one long text block. Strings in .NET are immutable; once you create a string, it cannot be changed in any way. If you want to add text to an existing string, .NET will instead create a brand-new string built from the original two immutable strings.

Although Char and String are different data types, you can easily move data back and forth between them, since they are both based on basic Unicode characters.

Date and Time Data Type

The System.DateTime data type lets you store either date or time values (or both) as data. Internally, DateTime is just a simple integer counter that displays a converted date or time format when needed. As a number, it counts the number of “ticks” since 12:00 a.m. on January 1, 1 AD. Each “tick” is exactly 100 nanoseconds, so it’s pretty precise. The maximum allowed date is December 31, 9999 in the Gregorian calendar.

Boolean Data Type

The System.Boolean data type represents the true essence of computer data: the bit. It holds one of two possible values: True or False. Shockingly, the data type actually requires between 2 and 4 bytes of data space to keep track of that single bit of data.

It turns out that Boolean values are very important in programs. As a developer, you are always testing to see whether various conditions are met before you process a block of code. All of these conditions eventually boil down to Boolean values and operations. .NET even has ways to easily migrate data between integer values and the Boolean data type. In such conversions, 0 becomes False, and the world of all other possible values becomes True. When moving from Boolean to an integer equivalent, False becomes 0 and True becomes −1. (If you ever use the C# language, you’ll find that it converts True to 1, not −1. Internally in .NET, True does convert to 1, but for historical reasons, Visual Basic uses −1. This difference normally isn’t a problem unless you store Boolean values as integers in a disk file and expect both Visual Basic and C# programs to interpret the data correctly.)

The System.Object Class

You already knew that .NET is an object-oriented development environment. What you probably didn’t know is that some pranksters at Microsoft placed a bet to see whether they could make the entire .NET system one big derived class. Well, the group that said it could be done won the bet. Everything in .NET—all code and all data—is derived from a single base class: System.Object. By itself, this class doesn’t have too many features. It can tell you its name, its type, and whether two instances of an object are in fact one and the same object. Other than that, it isn’t useful for much except to be used as a starting point for all other classes and types.

Because all classes in .NET—including all data types—derive from System.Object, you can treat an instance of any class (or data type) as Object. The data will remember what type it really is, so if you have a System.Int32 posing as System.Object, you can change it back to System.Int32 later.

Value Types and Reference Types

Back in Chapter 1, you read about the difference between value types and reference types: value types are buckets that contain actual data, and reference types contain instructions on where you can find the actual data. In general, value types contain simple and small data values, whereas reference types point to large and complex data blocks. This isn’t always true, but for most data you work with, it will be true.

System.Object is a reference type from which all other types and classes derive. This includes all the core data types, so you would think that they would be reference types as well. But there is another class stuck in between System.Object and most of the Visual Basic data types. This class, System.ValueType, implements the basic definition and usage of a value type. Table 6-4 lists some of the differences between value and reference types.

Table 6-4. Value type and reference type usage

Value types

Reference types

Ultimately derive from System.ValueType, which in turn derives from System.Object.

Ultimately derive from System.Object.

Derived core data types: Boolean, Byte, Char, DateTime, Decimal, Double, Int16, Int32, Int64, SByte, Single, UInt16, UInt32, UInt64.

Derived core data type: String.

Provide support for Visual Basic “structures.”

Provide support for Visual Basic “classes.”

Enumerations are derived as follows: System.ObjectSystem.ValueTypeSystem.Enum.

Delegates, used as references to class methods, are derived as follows: System.ObjectSystem.Delegate. One type of delegate, the “multicast delegate,” is further derived through System.MulticastDelegate.

Value types cannot derive from other classes or structures, nor can further structures derive from them.

Reference types can be derived from other classes, and can be used as base classes.

Instances cannot be set to Nothing. (Using a nullable type overcomes this limitation.)

Instances can be set to Nothing.

Instances can only contain data of the specified type. For instance, System.Int32 instances can only contain 32-bit signed integer data.

Instances usually refer to data of their defined type, but an instance can also point to a derived type. For example, an instance of System.String could refer to any data that used System.String as a base class.

Do not go through the full .NET garbage collection process.

Are destroyed through garbage collection.

(In addition to classes and structures, Visual Basic also defines “modules.” The .NET documentation identifies modules as reference types, but you can’t create instances of them.)

A value type can only contain data of its own type, but reference types can point to derived instances. This is important in .NET, since it was designed to allow a System.Object instance to refer to any data in an application. System.Object instances can refer to either value type or reference type data. For reference types, this is easy to understand since that instance will just point to some derived instance of itself. But if you assign a value type to a System.Object reference, .NET has to mark that instance in a special way to indicate that a reference type contains a value type. This process is called boxing, and the reverse process is called unboxing. Although boxing is useful, and sometimes essential, it comes with a substantial performance hit.

Visual Basic Data Types

All the data types implemented in the Visual Basic language are wrappers for the core .NET data types. Only some of the names have been changed to protect the innocent. Table 6-5 lists the Visual Basic data types and their .NET equivalents.

Table 6-5. Visual Basic data types and related .NET types

Visual Basic type

.NET type

Boolean

System.Boolean

Byte

System.Byte

Char

System.Char

Date

System.DateTime

Decimal

System.Decimal

Double

System.Double

Integer

System.Int32

Long

System.Int64

Object

System.Object

SByte

System.SByte

Short

System.Int16

Single

System.Single

String

System.String

UInteger

System.UInt32

ULong

System.UInt64

UShort

System.UInt16

All the Visual Basic data types are fully interchangeable with their .NET equivalents. Any instance of System.Int32 can be treated as though it were an instance of Integer, and vice versa.

Literals

The quickest way to include values of a particular data type in your Visual Basic code is to use a literal. You’ve already seen literals in action in this book. Chapter 1 included a literal in its sample project.

MsgBox("Hello, World!")

This call to the MsgBox function includes a String literal. String literals always appear within a set of double quotes. Most numeric literals appear with a data-type-defining character on the end of the literal, but there are other variations. Table 6-6 lists the different literal values you can include in your code.

Table 6-6. Literals supported by Visual Basic

Literal type

Example

Description

Boolean

True

The Boolean data type supports two literal values: True and False.

Char

"Q"c

Single-character literals appear in double quotes with a trailing character c. A literal of type Char is not the same as a single-character literal of type String.

Date

#11/7/2005#

Date or time literals appear between a set of number signs. You can include dates, times, or a combination of both. The date or time values can be in any format recognized by Windows, although Visual Studio may reformat your date literal for conformity with its own standards.

Decimal

123.45D

123.45@

Floating-point values of type Decimal are followed by a capital D, or the character @.

Double

123.45R

123.45#

Floating-point values of type Double are followed by a capital R, or the character #. Also, if you use a numeric literal with a decimal portion, but with no trailing data type character, that literal will be typed as a Double.

Hexadecimal

&HABCD

You can include hexadecimal literals in your code by starting the value with the “&H” character sequence, followed by the hex digits.

Integer

123.45I

123.45%

Integral values of type Integer are followed by a capital I, or the character %. Also, if you use a numeric literal that falls in the range of an Integer, but with no trailing data type character, that literal will be typed as an Integer.

Long

123.45L

123.45&

Integral values of type Long are followed by a capital L, or the character &. Also, if you use a numeric literal that falls in the range of a Long and outside the range of an Integer, but with no trailing data type character, that literal will be typed as a Long.

Octal

&O7654

You can include octal literals in your code by starting the value with the “&O” character sequence, followed by the octal digits.

Short

123.45S

Integral values of type Short are followed by a capital S.

Single

123.45F

123.45!

Floating-point values of type Single are followed by a capital F, or the character !.

String

"A ""B"" C"

String literals appear within a set of double quotes, with no special character following the closing quote. Use two quote characters within the string literal to embed a single quotation mark.

Constants

Literals are nice, but it isn’t always clear what they mean. Encountering the number 12 in a formula, for instance, might cause the formula to generate correct results, but it would still be helpful to know what 12 means. Is it the number of months in a year, the number of hours in a day, the minimum number of teeth in a mouth to eat steak, or something even more sinister?

Constants provide a way to assign meaningful names to literal values. They are treated a lot like literal values, but once defined, they can be used over and over again in your code. Each use of a literal value, even if it has the same value, represents a distinct definition and instance of that value.

In Visual Basic, constants are defined using the Const keyword.

Const MonthsInYear As Short = 12

This constant definition has the following parts:

A name

In this case, the name is MonthsInYear.

A data type

This example defines a Short constant. The data type always follows the As keyword. If you leave out this As clause, the constant’s data type will be whatever the assigned literal would have been on its own. Only the following data types can be used for constants: Boolean, Byte, Char, Date, Decimal, Double, Integer, Long, Object, SByte, Short, Single, String, UInteger, ULong, UShort, or the name of an enumeration (discussed in the next section).

An initializer

The initializer assigned here is 12. Once assigned, this value cannot be altered while your code is running. Constants are always value types, not reference types. Initializers are usually simple literals, but you can also include simple calculations:

Const Seven As Integer = 3 + 4
An access level

The definition of MonthsInYear listed here represents the typical format of a constant definition included within a code procedure. You can also define constants outside procedures, but still within a class or other type. When you do this, you generally add an access modifier keyword just before the Const keyword. This keyword indicates how much code will be able to use the constant. I’ll describe access modifiers a little later, in the section on variables. Constants defined within a procedure can only be used within that procedure.

Once you define a constant, you can use it anywhere you would use an equivalent literal.

Const GreatGreeting As String = "Hello, World!"
...Later...
MsgBox(GreatGreeting)

Enumerations

Enumerations, one of the core .NET types, allow you to group together named, related integer values as a set. Once bound together, the enumeration can be used like any other data type; you can create variables that are specific instances of an enumeration.

Enumerations are a multiline construct; the first line defines the name and underlying data type of the enumeration. Each enumeration member appears on a separate line, ending with a final closing End Enum line.

01  Enum CarType As Integer
02     Sedan = 1
03     StationWagon = 2
04     Truck = 3
05     SUV = 4
06     Other = 5
07  End Enum

The declaration line (line 01) includes the Enum keyword, the name of the enumeration (CarType), and the underlying data type (Integer). The As data type clause is optional; if you leave it off, the enumeration defaults to Integer. If you do supply a data type, it must be one of the following: Byte, Integer, Long, SByte, Short, UInteger, ULong, or UShort.

Each member of the enumeration (lines 02 to 06) must include at least a member name (such as Sedan). You can optionally assign a numeric value to some or all of the members, as I have done in the sample. If a member lacks an assignment, it is set to one more than the previous member. If none of the members have an assigned value, the first is assigned 0, the next 1, and so on.

Once defined, enumeration members act a lot like integer constants; you can use them anywhere you would normally use a literal or constant. When referencing the members of an enumeration in your code, include both the enumeration name and the member name.

CarType.Sedan

The Enum statement cannot be used within a method or procedure. Instead, you define an enumeration as a member of a type (class, structure, or module), or as its own standalone type, just like a class. The .NET Framework includes many useful predefined enumerations intended for use with framework features. For instance, the System.DayOfWeek enumeration includes members for each day of the week.

Variables

Literals are nice, and constants and enumerations are nicer, but none of them can be altered once your program starts. This tends to make your application rigid and inflexible. If all your customers are named “Fred” and they only place orders for $342.34, it probably won’t be much of a limitation. But most users want more variety in their software. Variables are named containers for data, just like constants, but their contents can be modified throughout the run of an application. Also, they can contain both value types and reference types. Here’s the basic syntax for defining a new variable:

Dim customerName As String

The Dim keyword—originally from the word dimension—defines a new variable; in this case, a variable named customerName with a data type of String. This named container is ready to hold any String value; assign to it string literals, other string variables, or the return value from functions that generate strings. Since it is a reference type, it can also be set to Nothing, a special Visual Basic value and keyword that means “this reference type is empty, really empty.”

customerName = Nothing                      ' Nothing
customerName = "Fred"                       ' Literal
customerName = GetCustomerName(customerID)  ' Function result

All variables contain their default value until set to something else. For reference types and nullable types, the default is Nothing; for numeric values, the default is 0. Booleans default to False. You can include an initial assignment as part of the Dim statement to override the default assignment.

Dim countdownSeconds As Short = 60
Dim processingDate As Date = Today
Dim customerName As String = GetCustomerName(customerID)

The last line in that code block shows a reference type—String—being assigned the String result of a function. You can also assign a brand-new instance of a reference instance to a reference type variable. And it’s new. That is, it uses the special New keyword, which says, “I’m creating a new instance of the specific data type.” There are a few different variations, but they all produce the same results.

' ----- One-line variation.
Dim someEmployee As New Employee

' ----- Another one-line variation.
Dim someEmployee As Employee = New Employee

' ----- Two-line variation.
Dim someEmployee As Employee
someEmployee = New Employee

Remember that reference types are buckets that contain directions for locating the actual data. When a reference variable first springs into existence, it contains Nothing. That is, the bucket contains no instructions at all since there is no related data stored anywhere. When you assign a new instance to a reference type variable, that instance gets stored somewhere in memory, and instructions for locating that data are dumped into the bucket. In the previous code block, each use of the New keyword creates a new data instance somewhere in memory. This data’s location is then assigned to the someString variable.

Many classes include one or more constructors, initialization routines that set up the initial values of the instance. You can call a specific constructor through the New clause. The String data type includes constructors that let you build an initial string. One of these special constructors lets you create a new string containing multiple copies of a specific character. The following statement assigns a string of 25 asterisks to the lotsOfStars variable:

Dim lotsOfStars As New String("*"c, 25)

Constructors are discussed in detail in Chapter 8.

Dim statements can appear anywhere in a procedure, but by tradition they appear right at the start of a procedure, before any other logic statements.

Sub MyProcedure(  )
   Dim myVariable As Integer
   ' ----- Additional code goes here...
End Sub

As with constants, variables can be defined either within a procedure, or outside a procedure but within a type. (Variables and constants declared outside a procedure are known as fields. Variables and constants declared inside a procedure are known as local variables and local constants, respectively.) The Dim keyword is always used with in-procedure variable declarations. At the type level, the Dim keyword is replaced by one of the following access modifiers:

Private

Private variables can be used by any member or procedure within the type, but nowhere else. If you derive a new class from a base class that includes a private type variable, the code in that derived class will have no access at all to that Private variable; it won’t even know it exists.

Friend

Friend variables are private to an assembly. They can be used by any code in their related type, but also by any code anywhere in the same assembly. Now that’s friendly.

Public

Public variables are available everywhere. It is possible to write an application or component that exposes its types to code beyond itself. Anything marked Public can be exposed in this way.

Protected

Protected variables are like Private type variables, but code in derived classes can also access them. You can use the Protected keyword only in a class definition; it doesn’t work in a structure or module.

Protected Friend

Protected Friend variables combine all the features of Friend and Protected. They can be used only in classes.

A single class or type may contain both fields and local variables and constants.

Class MyClass
   ' ----- Here's a field.
   Private InternalUseOnly As Boolean

   Sub MyProcedure(  )
      ' ----- Here's a local variable.
      Dim myVariable As Integer
   End Sub
End Class

There are other syntax variations to the Dim statement, some of which I will discuss later in this chapter and in other chapters.

Scope and Lifetime

When you define a variable within a procedure, it has procedure-level scope. This means you can use the variable anywhere within that procedure. Your procedure will likely have “block statements,” those statements, such as For...Next and If...Then, that require more than one line of source code to complete. If you add a Dim statement between the starting and ending lines of one of these statements, that declared variable will have only block-level scope. It will be available only within that block of the procedure.

For counter = 1 To 10
   Dim processResult As Integer
   ' ----- More  code here.
Next counter
MsgBox(processResult)  ' This line will fail

This code declares processResult within the For...Next block. So, it’s available only for use inside that block; any attempted use of processResult outside the For block generates an immediate error.

The lifetime of a procedure-level variable begins when the code first enters that procedure, and ends when the code exits the procedure. This is true for both procedure-level and block-level variables. This means that if you assign a block-level variable some value before exiting the block, it will still have that value if you reenter that block during the same procedure call.

For fields (class-level variables), the scope depends on the access level used when declaring the variable. The lifetime of a field begins when the class instance is created in code, and ends when the instance is destroyed or goes completely out of use.

Variable and Constant Naming Conventions

The names that you give to your variables will not have that much impact on how your application runs on the user’s workstation, but they can affect the clarity of the source code. In the days before .NET, many Windows programming languages used a system called Hungarian Notation to craft variable names. Such names helped to communicate information about the data type and usage of a variable to anyone reading the source code. Unfortunately, the rules used to define Hungarian variable names were somewhat complex, and varied not only among programming languages, but also among programmers using the same language.

When Microsoft released .NET back in 2002, its documentation included various programming recommendations. One of those recommendations was “Stop using the Java programming language.” Another recommendation encouraged programmers to cease from using Hungarian Notation, and instead embrace a new system that used casing rules to differentiate variables. The rules state that all variable names should employ mixed-case names (where each logical word in the variable name starts with a capital letter and continues with lowercase letters). The only differentiation comes in the capitalization of the initial letter:

  • Set the first letter of all local variables and all method parameters to lowercase. This is known as Camel Casing.

  • Set the first letter of all fields, methods, type members (including controls), and types to uppercase. This is known as Pascal Casing.

In the interest of full disclosure, I must tell you that I modified the original recommendations slightly from the documentation supplied with Visual Studio. The original rules were a little more complex when it came to field and method parameter names. Personally, I find the two rules listed here to be adequate for my needs.

You might give a local variable a name like lookInThisVariable, which capitalizes the first letter of each word, but not the initial letter. If you defined this variable as a field instead, you would change its name to LookInThisVariable, capitalizing the first letter.

Local Type Inference

Visual Basic is a strongly typed language. This means that all data values are either Integer, or Short, or String, or some other specific data type. Even the default Object data type is considered strong. To create a variable without a data type would be weak, and Visual Basic programmers are anything but weak.

Normally, you specifically tell Visual Basic what data type to use for a variable. But a new Visual Basic 2008 feature called local type inference lets the Visual Basic compiler join in the fun of assigning data types to variables. And what fun it is!

In standard variable declaration, you include the data type with an As clause.

Dim whatAmI As String
whatAmI = "You're a string, and nothing but a string."

But with local type inference, Visual Basic will figure out the data type all on its own when you leave off the As clause.

Dim thing1
Dim thing2
thing1 = "This is a string."
thing2 = 25
MsgBox(thing1.GetType.ToString)
MsgBox(thing2.GetType.ToString)

When you run this code, two messages appear to tell you the strong-type-name of each thing: System.String and System.Int32, respectively. (Don’t worry about the “GetType” stuff for now. It just identifies the true type of the things.) Visual Basic acts as though the first two lines looked like this:

Dim thing1 As String
Dim thing2 As Integer

Once Visual Basic identifies the data type for one of the as-of-yet-untyped variables, that variable is glued to that type. The following code will fail:

Dim thing1
thing1 = "This is a string."
thing1 = 25  ' This fails, since thing1 is a string.

As the name implies, local type inference works only with local variables. Class fields must be declared with a specific data type. Other restrictions apply. See dealer for details.

You can turn the type inference system on and off using the Option Infer statement at the top of each source code file.

Option Infer On

You can also set this on a project-wide basis through the Compile tab of the project properties.

Type inference exists to support the new LINQ features discussed in Chapter 17. Although you can let Visual Basic infer most or all of the variables in your application, it is not a good thing to do in practice. As smart as the compiler is, it doesn’t think deeply about the overall logic of your application, and it may make different data typing choices than you would. For instance, Visual Basic may infer a variable as Integer, even though you plan to stuff large Long values into it later. If you have the opportunity to include meaningful and accurate As clauses with your Dim statements, do it. Because I said so. Because it’s the right thing to do.

Operators

Visual Basic includes a variety of operators that let you manipulate the values of your variables. You’ve already seen the assignment operator (=), which lets you assign a value directly to a variable. Most of the other operators let you build up expressions that combine multiple original values in formulaic ways for eventual assignment to a variable. Consider the following statement:

squareArea = length * width

This statement includes two operators: assignment and multiplication. The multiplication operator combines two values (length and width) using multiplication, and the assignment operator stores the product in the squareArea variable. Without operators, you would be hard-pressed to calculate an area or any complex formula.

There are two types of non-assignment operators: unary and binary. Unary operators work with only a single value, or operand. Binary operators require two operands, but result in a single processed value. Operands include literals, constants, variables, and function return values. Table 6-7 lists the different operators with usage details.

Table 6-7. Visual Basic non-assignment operators

Operator

Description

+

Addition. Adds two operands together, producing a sum. Some programmers also use this operator to perform string concatenation, but it’s better to join strings using another operator (&) specifically designed for that purpose.

Syntax: operand1 + operand2

Example: 2 + 3

+

Unary plus. Ensures that an operand retains its current sign, either positive or negative. Since all operands automatically retain their sign, this operator is usually redundant. It may come in handy when we discuss “operator overloading” in Chapter 12.

Syntax: +operand

Example: +5

Subtraction. Subtracts one operand (the second) from another (the first), and returns the difference.

Syntax: operand1 - operand2

Example: 10 - 4

Unary negation. Reverses the sign of its operand. When used with a literal number, it results in a negative value. When used with a variable that contains a negative value, it produces a positive result.

Syntax: operand2

Example: −34

*

Multiplication. Multiplies two operands together, and returns the product.

Syntax: operand1 * operand2

Example: 8 * 3

/

Division. Divides one operand (the first) by another (the second), and returns the quotient. If the second operand contains zero, a divide-by-zero error occurs. (When working with Single and Double values, divide-by-zero actually returns special “infinity” or “not a number” indicators.)

Syntax: operand1 / operand2

Example: 9 / 3

Integer division. Divides one operand (the first) by another (the second), and returns the quotient, first truncating any decimal portion from that result. If the second operand contains zero, a divide-by-zero error occurs. (See the caveat listed with the / operator.)

Syntax: operand1 operand2

Example: 9 4

Mod

Modulo. Divides one operand (the first) by another (the second), and returns the remainder as an integer value. If the second operand contains zero, a divide-by-zero error occurs. (See the caveat listed with the / operator.)

Syntax: operand1 Mod operand2

Example: 10 Mod 3

^

Exponentiation. Raises one operand (the first) to the power of another (the second).

Syntax: operand1 ^ operand2

Example: 2 ^ 8

&

String concatenation. Joins two operands together, and returns a combined string result. Both operands are converted to their String equivalent before being joined together.

Syntax: operand1 & operand2

Example: "O" & "K"

And

Conjunction. Performs a logical or bitwise conjunction on two operands, and returns the result. For logical (Boolean) operations, the result will be True only if both operands evaluate to True. For bitwise (integer) operations, each specific bit in the result will be set to 1 only if the corresponding bits in both operands are 1.

Syntax: operand1 And operand2

Example: isOfficer And isGentleman

Or

Disjunction. Performs a logical or bitwise disjunction on two operands, and returns the result. For logical (Boolean) operations, the result will be True if either operand evaluates to True. For bitwise (integer) operations, each specific bit in the result will be set to 1 if the corresponding bit in either operand is 1.

Syntax: operand1 Or operand2

Example: enjoyMountains Or enjoySea

AndAlso

Short-circuited conjunction. This operator is equivalent to the logical version of the And operator, but if the first operand evaluates to False, the second operand will not be evaluated at all. This operator does not support bitwise operations.

Syntax: operand1 AndAlso operand2

Example: isOfficer AndAlso isGentleman

OrElse

Short-circuited disjunction. This operator is equivalent to the logical version of the Or operator, but if the first operand evaluates to True, the second operand will not be evaluated at all. This operator does not support bitwise operations.

Syntax: operand1 OrElse operand2

Example: enjoyMountains OrElse enjoySea

Not

Negation. Performs a logical or bitwise negation on a single operand. For logical (Boolean) operations, the result will be True if the operand evaluates to False, and False if the operand evaluates to True. For bitwise (integer) operations, each specific bit in the result will be set to 1 if the corresponding operand bit is 0, and set to 0 if the operand bit is 1.

Syntax: Not operand1

Example: Not readyToSend

Xor

Exclusion. Performs a logical or bitwise “exclusive or” on two operands, and returns the result. For logical (Boolean) operations, the result will be True only if the operands have different logical values (True or False). For bitwise (integer) operations, each specific bit in the result will be set to 1 only if the corresponding bits in the operands are different.

Syntax: operand1 Xor operand2

Example: chickenDish Xor beefDish

<<

Shift left. The Shift Left operator shifts the bits of the first operand to the left by the number of positions specified in the second operand, and returns the result. Bits pushed off the left end of the result are lost; bits added to the right end are always 0. This operator works best if the first operand is an unsigned integer value.

Syntax: operand1 << operand2

Example: &H25 << 3

>>

Shift right. The Shift Right operator shifts the bits of the first operand to the right by the number of positions specified in the second operand, and returns the result. Bits pushed off the right end of the result are lost; bits added to the left end are always the same as the bit originally in the leftmost position. This operator works best if the first operand is an unsigned integer value.

Syntax: operand1 >> operand2

Example: &H25 >> 2

=

Equals (comparison). Compares two operands and returns True if they are equal in value.

Syntax: operand1 = operand2

Example: expectedAmount = actualAmount

<>

Not equals. Compares two operands and returns True if they are not equal in value.

Syntax: operand1 <> operand2

Example: startValue <> endValue

<

Less than. Compares two operands and returns True if the first is less in value than the second. When comparing string values, the return is True if the first operand appears first when sorting the two strings.

Syntax: operand1 < operand2

Example: raiseRate < inflationRate

>

Greater than. Compares two operands and returns True if the first is greater in value than the second. When comparing string values, the return is True if the first operand appears last when sorting the two strings.

Syntax: operand1 > operand2

Example: raiseRate > inflationRate

<=

Less than or equal to. Compares two operands and returns True if the first is less than or equal to the value of the second.

Syntax: operand1 <= operand2

Example: raiseRate <= inflationRate

>=

Greater than or equal to. Compares two operands and returns True if the first is greater than or equal to the value of the second.

Syntax: operand1 >= operand2

Example: raiseRate >= inflationRate

Like

Pattern comparison. Compares the first operand to the pattern specified in the second operand, and returns True if there is a match. The pattern operand supports some basic wildcard and selection options, and is fully described in the documentation supplied with Visual Studio. .NET also includes a feature called regular expressions that provides a much more comprehensive pattern matching solution.

Syntax: operand1 Like operand2

Example: governmentID Like ssnPattern

Is

Type comparison. Compares the first operand to another object, a data type, or Nothing, and returns True if there is a match. I will document this operator in more detail later in the text, and in Chapter 8.

Syntax: operand1 Is operand2

Example: someVariable Is Nothing

IsNot

Negated type comparison. This operator is a shortcut for using the Is and Not operators together. The following two expressions are equivalent:

first IsNot second
Not (first Is second)

Syntax: operand1 IsNot operand2

Example: something IsNot somethingElse

TypeOf

Instance comparison. Returns the data type of a value or variable. The type of every class or data type in .NET is implemented as an object, based on System.Type. The TypeOf operator can be used only with the Is operator:

Syntax: TypeOf operand1 Is typeOperand

Example: TypeOf someVariable Is Integer

AddressOf

Delegate retrieval. Returns a delegate (described in Chapter 8) that represents a specific instance of a procedure or method.

Syntax: AddressOf method1

Example: AddressOf one.SomeMethod

GetType

Type retrieval. Returns the data type of a value or variable, just like the TypeOf operator. However, GetType works like a function, and does not need to be used with the Is operator.

Syntax: GetType(operand1)

Example: GetType(one)

Non-assignment operators use their operands to produce a result, but they do not cause the operands themselves to be altered in any way. The assignment operator does update the operand that appears on its left side. In addition to the standard assignment operator, Visual Basic includes several operators that combine the assignment operator with some of the binary operators. Table 6-8 lists these assignment operators.

Table 6-8. Visual Basic assignment operators

Operator

Based on

=

Standard assignment operator

+=

+ (addition)

−=

− (subtraction)

*=

* (multiplication)

/=

/ (division)

=

(integer division)

^=

^ (exponentiation)

&=

& (concatenation)

<<=

<< (shift left)

>>=

>> (shift right)

These assignment operators are just shortcuts for the full-bodied operators. For instance, to add 1 to a numeric variable, you can use either of these two statements:

' ----- Increment totalSoFar by 1.
totalSoFar = totalSoFar + 1

' ----- Another way to increment totalSoFar by 1.
totalSoFar += 1

Static Variables

Normally, the lifetime of a local procedure-level variable ends when the procedure ends. But sometimes you might want a variable to retain its value between each call into the procedure. Sometimes you might also want a million dollars, but you can’t always have it. But you can have variables that keep their values if you want. They’re called static variables. To declare a static variable, use the Static keyword in place of the Dim keyword.

Static keepingTrack As Integer = 0

The assignment of 0 to keepingTrack is done only once, when creating the instance of the type that contains this statement. Thereafter, it keeps whatever value is assigned to it until the instance is destroyed. Static variables can only be created within procedures.

Arrays

Software applications often work with sets of related data, not just isolated data values. Visual Basic includes two primary ways of working with such sets of data: collections (discussed in Chapter 16) and arrays. An array assigns a numeric position to each item included in the set, starting with 0 and ending with one less than the number of items included. An array of five items has elements numbering from 0 to 4.

As an example, imagine that you were developing a zoo simulation application. You might include an array named animals that includes each animal name in your zoo:

  • Animal #0: Aardvark

  • Animal #1: Baboon

  • Animal #2: Chimpanzee

  • Animal #3: Donkey

  • . . . and so on . . .

Visual Basic identifies array elements by a parenthesized number after the array name. For our animals, a simple assignment puts the String name of each animal in an array element.

animal(0) = "Aardvark"
animal(1) = "Baboon"
animal(2) = "Chimpanzee"
animal(3) = "Donkey"

Using each array element is just as easy.

MsgBox("The first animal is: " & animal(0))

Each element of an array is not so different from a standalone variable. In fact, you could just consider the set of animals in the example code to be distinct variables: a variable named animal(0), another variable named animal(1), and so on. But they are better than ordinary variables because you can process them as a set. For instance, you can scan through each element using a For...Next loop. Consider an Integer array named eachItem with elements numbered from 0 to 2. The following code block adds up the individual items of the array as though they were distinct variables:

Dim totalAmount As Integer
totalAmount = eachItem(0) + eachItem(1) + eachItem(2)

But since the items are in a numbered array, you can use a For...Next loop to scan through each element, one at a time.

Dim totalAmount As Integer = 0
For counter As Integer = 0 to 2
   ' ----- Keep a running total of the items.
   totalAmount += eachItem(counter)
Next counter

Before you assign values to array elements, or retrieve those elements, you must declare and size the array for your needs. The Dim statement creates an array just as it does ordinary variables; the ReDim statement resizes an array after it already exists.

Dim animal(0 To 25) As String  ' 26-element array
Dim moreAnimals(  ) As String   ' An undefined String array
ReDim moreAnimals(0 To 25)     ' Now it has elements

Normally, the ReDim statement would wipe out any existing data stored in each array element. Adding the Preserve keyword retains all existing data.

ReDim Preserve moreAnimals(0 to 30)  ' Keeps elements 0 to 25

Each element of the array is an independent object that can be assigned data as needed. In this example, each element is a String, but you can use any value type or reference type you wish in the array declaration. If you create an array of Object elements, you can mix and match the data in the array; element 0 need not contain the same type of data as element 1.

The array itself is also an independent object—a class instance that manages its set of contained elements. If you need to specify the entire array, and not just one of its elements (and there are times when you need to do this), use its name without any parentheses or positional values.

Multidimensional Arrays

Visual Basic arrays support more than one dimension (or “rank”). The dimensions indicate the number of independent ranges supported by the array. A one-dimensional array, like the animal array earlier, includes a single range. A two-dimensional array includes two comma-delimited ranges, forming a grid arrangement of elements, with separate ranges for rows and columns.

Dim ticTacToeBoard(0 To 2, 0 To 2) As Char  ' 3 × 3 board

An array can have up to 60 different dimensions, although there are usually better ways to organize data than breaking it out into that many dimensions.

Array Boundaries

The lower bound of any array dimension is normally 0, as indicated by the 0 To × clause when defining or redimensioning the array. You can actually leave the “0 To” part out of the statement, and just include the upper bound.

' ----- These two lines are equivalent.
Dim animal(0 To 25) As String
Dim animal(25) As String

These two statements both create an array with 26 elements, numbered 0 through 25.

There are a few special cases where nonzero lower bounds are allowed, such as when working with older COM-generated arrays. But the standard Visual Basic declaration syntax does not allow you to create arrays with nonzero lower bounds.

To determine the current lower or upper bound of an array dimension, use the LBound and UBound functions.

MsgBox("The board is " & (UBound(ticTacToeBoard, 1) + 1) & _
   " by " & (UBound(ticTacToeBoard, 2) + 1))

If your array includes only a single dimension, you don’t have to tell LBound or UBound which dimension you want to check.

MsgBox("The upper element is numbered " & UBound(animal))

Each array also includes GetLowerBound and GetUpperBound methods that return the same results as LBound and UBound. (I discuss methods in detail in Chapter 8.) However, the dimension number you pass to the GetLowerBound and GetUpperBound methods starts from 0, whereas LBound and UBound dimension values start the counting at 1.

MsgBox("The board is " & _
   (ticTacToeBoard.GetUpperBound(0) + 1) & _
   " by " & (ticTacToeBoard.GetUpperBound(1) + 1))

Initializing Arrays

Once you’ve declared your array elements, you can store and retrieve elements whenever you need. It’s also possible to store elements in your array right at declaration time. The list of new array elements appears in a set of curly braces.

Dim squares(  ) As Integer = {0, 1, 4, 9, 16, 25}

You must leave out the lower and upper bound specifications when creating an array in this way. The squares array shown here will have elements numbered 0 to 5.

Nullable Types

Value types are hard-working variables, maintaining their data values throughout their lives. Reference types work hard, too, but they can be filled with Nothing and get a little rest time. This difference has long been a thorn in the side of value types. Is it too much to ask to give these working-class variables a little down time?

Well, Microsoft has heard this plea, and starting with Visual Basic 2008, value types can now be assigned with Nothing. These new nullable types are essential when you want to have an undefined state for a standard value type (especially useful when working with database fields). Consider this class that manages employee information:

Public Class Employee
   Public Name As String
   Public HireDate As Date
   Public FireDate As Date

   Public Sub New(ByVal employeeName As String, _
         ByVal dateHired As Date)
      Me.Name = employeeName
      Me.HireDate = dateHired
   End Sub
End Class

This class works well, except that FireDate is not really correct. By default, FireDate will be set to January 1, 1, at midnight, and you can use that date as your “never fired” date. But what happens if your company really did fire someone just at that moment, two thousand years ago?

To resolve this issue, nullable types let you assign and retrieve Nothing from value type variables. These vitamin-enriched value types are declared using a special question-mark syntax.

' ----- Either of these two statements will work.
Public FireDate As Date?
Public FireDate2? As Date

I prefer the first syntax, with the question mark added to the data type. But either statement will work. Once it’s declared, a value type can take either standard data or Nothing.

FireDate = Nothing
FireDate = #7/18/2008#
If (FireDate Is Nothing) Then...

There is a special syntax when defining your own custom value types as nullable, but since it uses the “generics” Visual Basic feature, I’ll wait to introduce it until Chapter 16.

Common Visual Basic Functions

This final section includes a brief listing of the functions built into the Visual Basic language, many of which you will use regularly in your applications. Also listed here are some members of the Framework Class Library (FCL) that replicate features that were part of the Visual Basic language before .NET, but were moved into the framework for more general access. For the exact syntax required to use these functions, access the Visual Studio online help.

Conversion Functions

The conversion functions allow you to convert data of one Visual Basic data type to another. It’s not a free-for-all, so don’t go converting the string "hello" to an integer and expect it to work. But converting numbers from one numeric type to another, or converting numbers between string and numeric types, generally works just fine.

All of these statements (except CType) have the same basic syntax:

dest = CXxxx(source)

where source is the value to be converted by CXxxx. You don’t have to assign the result to a variable; you can use the result anywhere you would use a similar literal or variable value. Table 6-9 lists the built-in conversion functions.

Table 6-9. Visual Basic conversion functions

Function

Description

CBool

Converts a value to a Boolean.

CByte

Converts a value to a Byte.

CChar

Converts a value to a Char. If the source value is a string, only the first character is converted.

CDate

Converts a value to a Date. If the source value is a string, it must be in a valid date or time format.

CDbl

Converts a value to a Double.

CDec

Converts a value to a Decimal.

CInt

Converts a value to an Integer.

CLng

Converts a value to a Long.

CObj

Converts a value to an Object. This is useful when you want to store a value type as an Object instance.

CSByte

Converts a value to an SByte.

CShort

Converts a value to a Short.

CSng

Converts a value to a Single.

CType

Converts a value to any defined type, class, or interface, either in your application or in the FCL. The syntax is:

CType(sourceData, newType)

where newType is a data type. For instance:

CType(5, String)

converts the Integer 5 to a String. As with other conversion functions, you can’t convert data from one type to another if the types are incompatible, or if there is no conversion available that knows how to generate the target type from the source type. Operator overloading, discussed in Chapter 12, provides a way to let the CType function convert between types that would otherwise be incompatible.

CUInt

Converts a value to a UInteger.

CULng

Converts a value to a ULong.

CUShort

Converts a value to a UShort.

Date-Related Functions

Visual Basic includes several functions designed to manage date and time values. Table 6-10 lists these functions. Most of these functions accept one or more source arguments, and return either Date, String, or a numeric result.

Table 6-10. Visual Basic date-related functions and properties

Function

Description

DateAdd

Adds or subtracts a time or date value to a starting date. For instance, you can add 12 minutes, or subtract three years, from a given date.

DateDiff

Returns the difference between two date or time values. You can specify the interval, such as months or seconds.

DatePart

Returns one component of a date or time, such as the hour or the year.

DateSerial

Returns a Date built from specific month, day, and year values.

DateString

Returns the current date as a string. You can also set the date on the local computer using this keyword.

DateValue

Returns the date portion of a combined date and time value; the time portion is discarded.

Day

Returns the day from a given date value.

FormatDateTime

Formats a given date or time as a string, using a small set of predefined formats. This function is included for backward compatibility with older VBScript code.

Hour

Returns the hour from a given time value.

IsDate

Indicates whether the data supplied to this function is a valid date.

Minute

Returns the minute from a given time value.

Month

Returns the month from a given date value.

MonthName

Returns the name of a month for a numeric month value, 1 through 12.

Now

Returns the current date and time. Equivalent to TimeOfDay.

Second

Returns the seconds from a given time value.

TimeOfDay

Returns the current date and time. Equivalent to Now.

Timer

Returns the number of seconds that have elapsed since midnight of the current day. This function is reset to 0 each midnight.

TimeSerial

Returns a Date built from specific hour, minute, and second values.

TimeString

Returns the current time as a string. You can also set the time on the local computer using this keyword.

TimeValue

Returns the time portion of a combined date and time value; the date portion is discarded.

Today

Returns the current date.

Weekday

Returns an integer that indicates the day of the week.

WeekdayName

Returns the name of a weekday for an integer day of the week.

Year

Returns the year from a given date value.

Variables created as System.DateTime (or Visual Basic Date) each include several properties and methods that provide features similar to the functions listed in Table 6-10. For instance, the Second property returns the number of seconds.

Dim meetingTime As Date
meetingTime = #11/7/2005 8:00:03am#
MsgBox(meetingTime.Second)   ' Displays '3'
MsgBox(Second(meetingTime))  ' Also displays '3'

You can use either the intrinsic Visual Basic functions or the equivalent System.DateTime methods and properties in your code. Each technique provides the same result.

Numeric Functions

Visual Basic programmers just love working with numbers; it’s in their blood. Fortunately, Visual Basic includes lots of features for working wonders with numbers. In addition to the standard data manipulation operators, Table 6-11 lists several number-related functions.

Table 6-11. Visual Basic number-related functions

Function

Description

Fix

Truncates the decimal portion of a number, returning only the whole portion. Similar to the Int function.

FormatCurrency

Formats a given number as a currency value, using a small set of predefined formats. This function is included for backward compatibility with older VBScript code.

FormatNumber

Formats a given number as a general number, using a small set of predefined formats. This function is included for backward compatibility with older VBScript code.

FormatPercent

Formats a given number as a percentage, using a small set of predefined formats. This function is included for backward compatibility with older VBScript code.

Hex

Formats a number as hexadecimal, and returns its string representation.

Int

Returns the whole number that is less than or equal to the supplied value. Similar to the Fix function.

IsNumeric

Indicates whether the data supplied to this function is a valid number.

Oct

Formats a number as octal, and returns its string representation.

Val

Extracts the first valid number from a string and returns it.

The .NET Framework includes the System.Math class, which contains several math-related function members. Some of these, such as Round, Sin, and Log, were implemented as intrinsic functions in Visual Basic 6.0, but have been moved from the language to the Math class in .NET.

Visual Basic also includes several functions used for financial and accounting calculations. These functions were also included in Visual Basic 6.0. As they are not relevant to the project discussed in this book, I will only list their names here: DDB, FV, IPmt, IRR, MIRR, NPer, NPV, Pmt, PPmt, PV, Rate, SLN, and SYD.

String Functions

String manipulation is a core part of Windows programming. The new XML features included with .NET are really just fancy string-manipulation routines, although with the complexities hidden from view. Visual Basic includes many functions designed to manipulate strings and characters. They are listed in Table 6-12.

As with most functions, these functions return a new string or value, leaving the original string or source values intact. The lone exception is the Mid statement, which modifies the source variable’s value.

Table 6-12. Visual Basic string-related functions

Function

Description

Asc, AscW

Returns the numeric ASCII or Unicode value for a character.

Chr, ChrW

Given a number, these functions return the matching ASCII or Unicode character.

Filter

Returns an array that is a subset of a source array, but including only those elements that matched a pattern.

Format

Formats number, date, and time values using predefined or custom formatting codes.

GetChar

Extracts a single character from a larger string.

InStr

Returns the position of a substring within a larger string.

InStrRev

Returns the position of a substring within a larger string, searching from the end of the string until the beginning.

Join

Returns a string built from a concatenation of an array of strings.

LCase

Converts a string to its lowercase equivalent.

Left

Returns the leftmost portion of a string.

Len

Returns the length of a string.

LSet

Left-aligns a string within a larger string of spaces.

LTrim

Removes spaces from the start of a string.

Mid

Extracts a substring from the middle of a larger string.

Mid statement

Modifies a range of characters in an existing string with new content. This is not a function, but a special Visual Basic statement. Its syntax varies considerably from that of most other Visual Basic features.

Replace

Replaces occurrences of a substring with another substring, all within a larger string.

Right

Returns the rightmost portion of a string.

RSet

Right-aligns a string within a larger string of spaces.

RTrim

Removes spaces from the end of a string.

Space

Generates a string containing a specified number of space characters. Similar to the StrDup function.

Split

Splits a string into an array of substrings based on a delimiter.

Str

Converts a number to its string representation.

StrComp

Compares two strings, and returns an integer indicating their sort order.

StrConv

Converts a string to a new format based on a conversion code. Some of the conversions involve changing the case of the content.

StrDup

Generates a string containing a specified number of a given character. Similar to the Space function, but works with any character.

StrReverse

Reverses the characters in a string.

Trim

Removes spaces from the start and end of a string.

UCase

Converts a string to its uppercase equivalent.

Variables created as System.String (or Visual Basic String) each include several properties and methods that provide features similar to the functions listed in Table 6-12. For instance, the Length property returns the number of characters in the string.

Dim simpleString As String = "abcde"
MsgBox(simpleString.Length)   ' Displays '5'
MsgBox(Len(simpleString))     ' Also displays '5'

You can use either the intrinsic Visual Basic functions or the equivalent System.String methods and properties in your code. Each technique provides the same result, although the syntax details and options may vary.

Other Functions

Visual Basic includes several functions that refuse to be squeezed into any of the other categories. Table 6-13 documents these functions.

Table 6-13. Visual Basic miscellaneous functions

Function

Description

DirectCast

Converts a value from one data type to another, although the starting and ending data types must be related. Similar to the TryCast and CType functions.

ErrorToString

Returns the string representation of an error code. This works only with the system error codes previously available in Visual Basic 6.0, although these codes are still available in .NET.

IsArray

Indicates whether the data supplied to this function is a valid array.

IsDBNull

Indicates whether the data supplied to this function is a NULL database value.

IsError

Indicates whether the data supplied to this function is an error condition.

IsNothing

Indicates whether the data supplied to this function is undefined, or set to Nothing.

IsReference

Indicates whether the data supplied to this function is a reference type or a value type.

QBColor

Returns a color code from a small set of predefined colors.

RGB

Returns a color code built from the individual red, green, and blue components.

SystemTypeName

Given a Visual Basic data type name, this function returns the equivalent .NET data type name.

TryCast

Converts a value from one data type to another, although the starting and ending data types must be related. Similar to the DirectCast and CType functions.

TypeName

Returns a data type name that summarizes the data type of the supplied content. The returned string is a generalized summary, and not necessarily the “true” data type name.

VarType

Returns a code indicating the general data type of the supplied content.

Summary

When you’re working with Visual Basic, you’re working with data. The data types included with Visual Basic are simply wrappers for the core data types in .NET, but Visual Basic also adds many functions and features that enhance your ability to manage and organize data.

Project

You look tired. Why don’t you take a five-minute break, and then we’ll dive into the project code.

Welcome back! In this chapter, we’ll use the data type and function features we read about to design some general support routines that will be used throughout the program. All of this code will appear in a Visual Basic module named General, all stored in a project file named General.vb.

PROJECT ACCESS

Load the Chapter 6 (Before) Code project, either through the New Project templates or by accessing the project directly from the installation directory. To see the code in its final form, load Chapter 6 (After) Code instead.

I’ve already added the General.vb file with its module starting and ending blocks.

Friend Module General

End Module

All the code we add in this chapter will appear between these two lines. Remember, modules are a lot like classes and structures, but you can’t create instances of them; all their members are shared with all parts of your source code. This allows them to be used anywhere in the application. We don’t need to do anything special to make them available to the entire program, other than to set the access level of each member as needed.

First, we’ll add some general constants used throughout the program. Back in Visual Basic 6.0, I would have called these “global constants.” But now they are simply shared members of the General module. Add the following code just below the Module General statement.

INSERT SNIPPET

Insert Chapter 6, Snippet Item 1.

' ----- Public constants.
Public Const ProgramTitle As String = "The Library Project"
Public Const NotAuthorizedMessage As String = _
   "You are not authorized to perform this task."
Public Const UseDBVersion As Integer = 1

' ----- Constants for the MatchingImages image list.
Public Const MatchPresent As Integer = 0
Public Const MatchNone As Integer = 1
Public Enum LookupMethods As Integer
   ByTitle = 1
   ByAuthor = 2
   ...remaining items excluded for brevity...
End Enum

Public Enum LibrarySecurity As Integer
   ManageAuthors = 1
   ...remaining items excluded for brevity...
   ViewAdminPatronMessages = 23
End Enum
Public Const MaxLibrarySecurity As LibrarySecurity = _
   LibrarySecurity.ViewAdminPatronMessages

These constants and enumerations are pretty self-explanatory based on their Pascal-cased names. UseDBVersion will be used to ensure that the application matches the database being used when multiple versions of each are available. The MatchPresent and MatchNone constants will be used for library item lookups.

The two enumerations define codes that specify the type of library item lookup to perform (LookupMethods), and the security codes used to limit the features that a specific administrator will be able to perform in the application (LibrarySecurity).

It’s time to add some methods. The first method, CenterText, centers a line of text within a specific width. For instance, if you had the string "Hello, World" (12 characters in length) and you wanted to center it on a line that could be up to 40 characters long, you would need to add 14 spaces to the start of the line (determined by subtracting 12 from 40, and then dividing the result by 2). The routine uses a couple of the string-specific Visual Basic functions (such as Trim, Left, and Len) to manipulate and test the data, and the integer division operator to help calculate the number of spaces to insert.

INSERT SNIPPET

Insert Chapter 6, Snippet Item 2.

Public Function CenterText(ByVal origText As String, _
      ByVal textWidth As Integer) As String
   ' ----- Center a piece of text in a field width. If the
   '       text is too wide, truncate it.
   Dim resultText As String

   resultText = Trim(origText)
   If (Len(resultText) >= textWidth) Then
      ' ----- Truncate as needed.
      Return Trim(Left(origText, textWidth))
   Else
      ' ----- Start with extra spaces.
      Return Space((textWidth - Len(origText))  2) & _
         resultText
   End If
End Function

The function starts by making a copy of the original string (origText), removing any extra spaces with the Trim function. It then tests that result to see whether it will even fit on the line. If not, it chops off the trailing characters that won’t fit, and returns that result. For strings that do fit on a line textWidth characters wide, the function adds the appropriate number of spaces to the start of the string, and returns the result.

Code snippet #2 also added a function named LeftAndRightText. It works just like CenterText, but it puts two distinct text strings at the extreme left and right ends of a text line. Any questions? Great. Let’s move on.

Code snippet #3 adds a routine named DigitsOnly. It builds a new string made of just the digits found in a source string, origText. It does this by calling the IsNumeric function for each character in origText, one at a time. Each found digit is then concatenated to the end of destText.

INSERT SNIPPET

Insert Chapter 6, Snippet Item 3.

Public Function DigitsOnly(ByVal origText As String) As String
   ' ----- Return only the digits found in a string.
   Dim destText As String
   Dim counter As Integer

   ' ----- Examine each character.
   destText = ""
   For counter = 1 To Len(origText)
      If (IsNumeric(Mid(origText, counter, 1))) Then _
         destText &= Mid(origText, counter, 1)
   Next counter
   Return destText
End Function

The last two functions, CountSubStr and GetSubStr, count and extract substrings from larger strings, based on a delimiter. Visual Basic includes two functions, Mid and GetChar, that also extract substrings from larger strings, but these are based on the position of the substring. The CountSubStr and GetSubStr functions examine substrings by first using a delimiter to break the larger string into pieces.

INSERT SNIPPET

Insert Chapter 6, Snippet Item 4.

The CountSubStr function counts how many times a given substring appears in a larger string. It uses Visual Basic’s InStr function to find the location of a substring (subText) in a larger string (mainText). It keeps doing this until it reaches the end of mainText, maintaining a running count (totalTimes) of the number of matches.

Public Function CountSubStr(ByVal mainText As String, _
      ByVal subText As String) As Integer
   ' ----- Return a count of the number of times that
   '       a subText occurs in a string (mainText).
   Dim totalTimes As Integer
   Dim startPos As Integer
   Dim foundPos As Integer

   totalTimes = 0
   startPos = 1

   ' ----- Keep searching until we don't find it no more!
   Do
      ' ----- Search for the subText.
      foundPos = InStr(startPos, mainText, subText)
      If (foundPos = 0) Then Exit Do
      totalTimes += 1

      ' ----- Move to just after the occurrence.
      startPos = foundPos + Len(subText)
   Loop

   ' ----- Return the count.
   Return totalTimes
End Function

Just to be more interesting than I already am, I used a different approach to implement the GetSubStr function. This function returns a delimited section of a string. For instance, the following statement gets the third comma-delimited portion of bigString:

bigString = "abc,def,ghi,jkl,mno"
MsgBox(GetSubStr(bigString, ",", 3))  ' Displays: ghi

I used Visual Basic’s Split function to break the original string (origString) into an array of smaller strings (stringParts), using delim as the breaking point. Then I return element number whichField from the result. Since whichField starts with 1 and the array starts at 0, I must adjust the position to return the correct element.

Public Function GetSubStr(ByVal origString As String, _
      ByVal delim As String, ByVal whichField As Integer) _
      As String
   ' ----- Extracts a delimited string from another
   '       larger string.
   Dim stringParts(  ) As String

   ' ----- Handle some errors.
   If (whichField < 0) Then Return ""
   If (Len(origString) < 1) Then Return ""
   If (Len(delim) = 0) Then Return ""

   ' ----- Break the string up into delimited parts.
   stringParts = Split(origString, delim)
   ' ----- See whether the part we want exists and return it.
   If (whichField > UBound(stringParts) + 1) Then Return "" _
      Else Return stringParts(whichField − 1)
End Function

If these functions seem simple to you, great! Most Visual Basic code is no more difficult than these examples. Sure, you might use some unfamiliar parts of the FCL, or interact with things more complicated than strings and numbers. But the overall structure will be similar. Most source code is made up of assignment statements, tests using the If statement, loops through data using a For...Next or similar statement, and function calls. And that’s just what we did in these short methods.

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

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