Chapter 15. Data Types, Variables, and Constants

Variables are among the most fundamental building blocks of a program. A variable is a program object that stores a value. The value can be a number, letter, string, date, structure containing other values, or an object representing both data and related actions.

When a variable contains a value, the program can manipulate it. It can perform arithmetic operations on numbers, string operations on strings (concatenation, calculating substrings, finding a target within a string), date operations (find the difference between two dates, add a time period to a date), and so forth.

Four factors determine a variable's exact behavior:

  • Data type determines the kind of the data (integer, character, string, and so forth).

  • Scope defines the code that can access the variable. For example, if you declare a variable inside a For loop, only other code inside the For loop can use the variable. If you declare a variable at the top of a subroutine, all the code in the subroutine can use the variable.

  • Accessibility determines what code in other modules can access the variable. If you declare a variable at the module level (outside of any subroutine in the module) and you use the Private keyword, only the code in the module can use the variable. If you use the Public keyword, code in other modules can use the variable as well.

  • Lifetime determines how long the variable's value is valid. A variable inside a subroutine that is declared with a normal Dim statement is created when the subroutine begins and is destroyed when it exits. If the subroutine runs again, it creates a new copy of the variable and its value is reset. If the variable is declared with the Static keyword, however, the same instance of the variable is used whenever the subroutine runs. That means the variable's value is preserved between calls to the subroutine.

For example, a variable declared within a subroutine has scope equal to the subroutine. Code outside of the subroutine cannot access the variable. If a variable is declared on a module level outside any subroutine, it has module scope. If it is declared with the Private keyword, it is accessible only to code within the module. If it is declared with the Public keyword, then it is also accessible to code outside of the module.

Visibility is a concept that combines scope, accessibility, and lifetime. It determines whether a certain piece of code can use a variable. If the variable is accessible to the code, the code is within the variable's scope, and the variable is within its lifetime (has been created and not yet destroyed), the variable is visible to the code.

This chapter explains the syntax for declaring variables in Visual Basic. It explains how you can use different declarations to determine a variable's data type, scope, accessibility, and lifetime. It discusses some of the issues you should consider when selecting a type of declaration, and describes some newer variable concepts such as anonymous and nullable types, which can complicate variable declarations. This chapter also explains ways you can initialize objects, arrays, and collections quickly and easily.

Constants, parameters, and property procedures all have concepts of scope and data type that are similar to those of variables, so they are also described here.

The chapter finishes with a brief explanation of naming conventions. Which naming rules you adopt isn't as important as the fact that you adopt some. This chapter discusses where you can find the conventions used by Microsoft Consulting Services. From those, you can build your own coding conventions.

DATA TYPES

The following table summarizes Visual Basic's elementary data types.

TYPE

SIZE

VALUES

Boolean

2 bytes

True or False

Byte

1 byte

0 to 255 (unsigned byte)

SByte

1 byte

−128 to 127 (signed byte)

Char

2 bytes

0 to 65,535 (unsigned character)

Short

2 bytes

−32,768 to 32,767

UShort

2 bytes

0 through 65,535 (unsigned short)

Integer

4 bytes

−2,147,483,648 to 2,147,483,647

UInteger

4 bytes

0 through 4,294,967,295 (unsigned integer)

Long

8 bytes

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

ULong

8 bytes

0 through 18,446,744,073,709,551,615 (unsigned long)

Decimal

16 bytes

0 to +/−79,228,162,514,264,337,593,543,950,335 with no decimal point. 0 to +/−7.9228162514264337593543950335 with 28 places to the right of the decimal place.

Single

4 bytes

−3.4028235E+38 to −1.401298E-45 (negative values) 1.401298E-45 to 3.4028235E+38 (positive values)

Double

8 bytes

−1.79769313486231570E+308 to −4.94065645841246544E-324 (negative values) 4.94065645841246544E-324 to 1.79769313486231570E+308 (positive values)

String

variable

Depending on the platform, a string can hold approximately 0 to 2 billion Unicode characters

Date

8 bytes

January 1, 0001 0:0:00 to December 31, 9999 11:59:59 pm

Object

4 bytes

Points to any type of data

Structure

Variable

Structure members have their own ranges.

The System namespace also provides integer data types that specify their number of bits explicitly. For example, Int32 represents a 32-bit integer. Using these values instead of Integer emphasizes the fact that the variable uses 32 bits. That can sometimes make code clearer. For example, suppose that you need to call an application programming interface (API) function that takes a 32-bit integer as a parameter. In Visual Basic 6, a Long uses 32 bits but in Visual Basic .NET, an Integer uses 32 bits. You can make it obvious that you are using a 32-bit integer by giving the parameter the Int32 type.

The data types that explicitly give their sizes are Int16, Int32, Int64, UInt16, UInt32, and UInt64.

The Integer data type is usually the fastest of the integral types. You will generally get better performance using Integers than you will with the Char, Byte, Short, Long, or Decimal data types. You should stick with the Integer data type unless you need the extra range provided by Long and Decimal, or you need to save space with the smaller Char and Byte data types. In many cases, the space savings you will get using the Char and Byte data types isn't worth the extra time and effort, unless you are working with a very large array of values.

Note that you cannot safely assume that a variable's storage requirements are exactly the same as its size. In some cases, the program may move a variable so that it begins on a boundary that is natural for the hardware platform. For example, if you make a structure containing several Short (2-byte) variables, the program may insert 2 extra bytes between them so they can all start on 4-byte boundaries because that may be more efficient for the hardware. For more information on structures, see Chapter 26, "Classes and Structures."

Some data types also come with some additional overhead. For example, an array stores some extra information about each of its dimensions.

TYPE CHARACTERS

Data type characters identify a value's data type. The following table lists the data type characters of Visual Basic.

CHARACTER

DATA TYPE

%

Integer

&

Long

@

Decimal

!

Single

#

Double

$

String

You can specify a variable's data type by adding a data type character after a variable's name when you declare it. When you use the variable later, you can omit the data type character if you like. For example, the following code declares variable num_desserts as a Long and satisfaction_quotient as a Double. It then assigns values to these variables.

Dim num_desserts&
Dim satisfaction_quotient#

num_desserts = 100
satisfaction_quotient# = 1.23

If you have Option Explicit turned off, you can include a data type character the first time you use the variable to determine its data type. If you omit the character, Visual Basic picks a default data type based on the value you assign to the variable.

If the value you assign is an integral value that will fit in an Integer, Visual Basic makes the variable an Integer. If the value is too big for an Integer, Visual Basic makes the variable a Long. If the value contains a decimal point, Visual Basic makes the variable a Double.

The following code shows the first use of three variables (Option Explicit is off). The first statement sets the variable an_integer equal to the value 100. This value fits in an Integer, so Visual Basic makes the variable an Integer. The second statement sets a_long equal to 10000000000. That value is too big to fit in an Integer, so Visual Basic makes it a Long. The third statement sets a_double to 1.0. That value contains a decimal point, so Visual Basic makes the variable a Double.

an_integer = 100
a_long = 10000000000
a_double = 1.0

If you set a variable equal to a True or False, Visual Basic makes it a Boolean.

In Visual Basic, you surround date values with # characters. If you assign a variable to a date value, Visual Basic gives the variable the Date data type. The following code assigns Boolean and Date variables:

a_boolean = True
a_date = #12/31/2007#

In addition to data type characters, Visual Basic provides a set of literal type characters that determine the data type of literal values. These are values that you explicitly type into your code in statements such as assignment and initialization statements. The following table lists the literal type characters of Visual Basic.

CHARACTER

DATA TYPE

S

Short

US

UShort

I

Integer

UI

UInteger

L

Long

UL

ULong

D

Decimal

F

Single (F for floating point)

R

Double (R for real)

c

Char (note lowercase c)

A literal type character determines the data type of a literal value in your code and may indirectly determine the data type of a variable assigned to it. For example, suppose that the following code is the first use of the variables i and ch (with Option Explicit turned off).

i = 123L
ch = "X"c

Normally, Visual Basic would make i an Integer, because the value 123 fits in an Integer. Because the literal value 123 ends with the L character, however, the value is a Long, so the variable i is also a Long.

Similarly, Visual Basic would normally make variable ch a String because the value "X" looks like a string. The c following the value tells Visual Basic to make this a Char variable instead.

Visual Basic also lets you precede a literal integer value with &H to indicate that it is hexadecimal (base 16) or &O to indicate that it is octal (base 8). For example, the following three statements set the variable flags to the same value. The first statement uses the decimal value 100, the second uses the hexadecimal value &H64, and the third uses the octal value &O144.

flags = 100       ' Decimal 100.
flags = &H64  ' Hexadecimal &H64 = 6 * 16 + 4 = 96 + 4 = 100.
flags = &O144 ' Octal &O144 = 1 * 64 + 4 * 8 + 4 = 64 + 32 + 4 = 100.

Sometimes you must use literal type characters to make a value match a variable's data type. For example, consider the following code:

Dim ch As Char
ch = "X"  ' Error because "X" is a String.
ch = "X"c ' Okay because "X"c is a Char.

Dim amount As Decimal
amount = 12.34  ' Error because 12.34 is a Double.
amount = 12.34D ' Okay because 12.34D is a Decimal.

The first assignment tries to assign the value "X" to a Char variable. This throws an error because "X" is a String value so it won't fit in a Char variable. Although it is obvious to a programmer that this code is trying to assign the character X to the variable, Visual Basic thinks the types don't match.

The second assignment statement works because it assigns the Char value "X"c to the variable. The next assignment fails when it tries to assign the Double value 12.34 to a Decimal variable. The final assignment works because the value 12.34D is a Decimal literal.

The following code shows another way to accomplish these assignments. This version uses the data type conversion functions CChar and CDec to convert the values into the proper data types. The following section, "Data Type Conversion," has more to say about data type conversion functions.

ch = CChar("X")
amount = CDec(12.34)

Using data type characters, literal type characters, and the Visual Basic default data type assignments can lead to very confusing code. You cannot expect every programmer to notice that a particular variable is a Single because it is followed by ! in its first use, but not in others. You can make your code less confusing by using variable declarations that include explicit data types.

DATA TYPE CONVERSION

Normally, you assign a value to a variable that has the same data type as the value. For example, you assign a string value to a String variable, you assign an integer value to an Integer variable, and so forth. Whether you can assign a value of one type to a variable of another type depends on whether the conversion is a narrowing or widening conversion.

Narrowing Conversions

A narrowing conversion is one where data is converted from one type to another type that cannot hold all of the possible values allowed by the original data type. For example, the following code copies the value from a Long variable into an Integer variable. A Long value can hold values that are too big to fit in an Integer, so this is a narrowing conversion. The value contained in the Long variable may or may not fit in the Integer.

Dim an_integer As Integer
Dim a_long As Long
...
an_integer = a_long

The following code shows a less obvious example. Here the code assigns the value in a String variable to an Integer variable. If the string happens to contain a number (for example "10" or "1.23"), the assignment works. If the string contains a non-numeric value (such as "Hello"), however, the assignment fails with an error.

Dim an_integer As Integer
Dim a_string As String
...
an_integer = a_string

Another non-obvious narrowing conversion is from a class to a derived class. Suppose that the Employee class inherits from the Person class. Then setting an Employee variable equal to a Person object, as shown in the following code, is a narrowing conversion because you cannot know without additional information whether the Person is a valid Employee. All Employees are Persons, but not all Persons are Employees.

Dim an_employee As Employee
Dim a_person As Person
...
an_employee = a_person

If you have Option Strict turned on, Visual Basic will not allow implicit narrowing conversions. If Option Strict is off, Visual Basic will attempt an implicit narrowing conversion and throw an error if the conversion fails (for example, if you try to copy the Integer value 900 into a Byte variable).

To make a narrowing conversion with Option Strict turned on, you must explicitly use a data type conversion function. Visual Basic will attempt the conversion and throw an error if it fails. The CByte function converts a numeric value into a Byte value, so you could use the following code to copy an Integer value into a Byte variable:

Dim an_integer As Integer
Dim a_byte As Byte
...
a_byte = CByte(an_integer)

If the Integer variable contains a value less than 0 or greater than 255, the value will not fit in a Byte variable so CByte throws an error.

The following table lists the data type conversion functions of Visual Basic.

FUNCTION

CONVERTS TO

CBool

Boolean

CByte

Byte

CChar

Char

CDate

Date

CDbl

Double

CDec

Decimal

CInt

Integer

CLng

Long

CObj

Object

CSByte

SByte

CShort

Short

CSng

Single

CStr

String

CUInt

UInteger

CULng

ULong

CUShort

UShort

The CInt and CLng functions round fractional values off to the nearest whole number. If the fractional part of a number is exactly .5, the functions round to the nearest even whole number. For example, 0.5 rounds to 0, 0.6 rounds to 1, and 1.5 rounds to 2.

In contrast, the Fix and Int functions truncate fractional values. Fix truncates toward zero, so Fix(?0.9) is 0 and Fix(0.9) is 0. Int truncates downward, so Int(?0.9) is ?1 and Int(0.9) is 0.

Fix and Int also differ from CInt and CLng because they return the same data type they are passed. CInt always returns an Integer no matter what type of value you pass it. If you pass a Long into Fix, Fix returns a Long. In fact, if you pass a Double into Fix, Fix returns a Double.

The CType function takes as parameters a value and a data type, and it converts the value into that type if possible. For example, the following code uses CType to perform a narrowing conversion from a Long to an Integer. Because the value of a_long can fit within an integer, the conversion succeeds.

Dim an_integer As Integer
Dim a_long As Long = 100
an_integer = Ctype(a_long, Integer)

The DirectCast statement changes value types much as CType does, except that it only works when the variable it is converting implements or inherits from the new type. For example, suppose the variable dessert_obj has the generic type Object and you know that it points to an object of type Dessert. Then the following code converts the generic Object into the specific Dessert type:

Dim dessert_obj As Object = New Dessert("Ice Cream")
Dim my_dessert As Dessert
my_dessert = DirectCast(dessert_obj, Dessert)

DirectCast throws an error if you try to use it to change the object's data type. For example, the following code doesn't work, even though converting an Integer into a Long is a narrowing conversion:

Dim an_integer As Integer = 100
Dim a_long As Long
a_long = DirectCast(an_integer, Long)

The TryCast statement converts data types much as DirectCast does, except that it returns Nothing if there is an error, rather than throwing an error.

Data Type Parsing Methods

Each of the fundamental data types (except for String) has a Parse method that attempts to convert a string into the variable type. For example, the following two statements both try to convert the string value txt_entered into an Integer:

Dim txt_entered As String = "112358"
Dim num_entered As Integer
...
num_entered = CInt(txt_entered) ' Use CInt.
num_entered = Integer.Parse(txt_entered) ' Use Integer.Parse.

Some of these parsing methods can take additional parameters to control the conversion. For example, the numeric methods can take a parameter that gives the international number style the string should have.

The class parsing methods have a more object-oriented feel than the conversion functions. They are also a bit faster. They only parse strings, however, so if you want to convert from a Long to an Integer, you need to use CInt rather than Integer.Parse or Int32.Parse.

Widening Conversions

In contrast to a narrowing conversion, a widening conversion is one where the new data type is always big enough to hold the old data type's values. For example, a Long is big enough to hold any Integer value, so copying an Integer value into a Long variable is a widening conversion.

Visual Basic allows widening conversions. Note that some widening conversions can still result in a loss of data. For example, a Decimal variable can store more significant digits than a Single variable can. A Single can hold any value that a Decimal can but not with the same precision. If you assign a Decimal value to a Single variable, you may lose some precision.

The Convert Class

The Convert class provides an assortment of methods for converting a value from one data type to another. For example, the following code uses the ToInt32 method to convert the string "17" into a 32-bit integer:

Dim i As Integer = Convert.ToInt32("17")

These methods are easy to understand so they make code simple to read. Unfortunately they work with particular data type sizes such as 16- or 32-bit integer rather than with the system's default integer size so they may require editing in the future. For example, if a later version of Visual Basic assumes 64-bit integers, then you may need to update your calls to Convert methods.

ToString

The ToString method is a conversion function that is so useful it deserves special mention. Every object has a ToString method that returns a string representation of the object. For example, the following code converts the integer value num_employees into a string:

Dim txt As String = num_employees.ToString()

Exactly what value ToString returns depends on the object. For example, a double's ToString method returns the double formatted as a string. More complicated objects tend to return their class names rather than their values (although you can change that behavior by overriding their ToString methods).

ToString can take as a parameter a format string to change the way it formats its result. For example, the following code displays the value of the double angle with two digits after the decimal point:

MessageBox.Show(angle.ToString("0.00"))

Appendix P, "Date and Time Format Specifiers," and Appendix Q, "Other Format Specifiers," describe format specifiers in greater detail.

VARIABLE DECLARATIONS

The complete syntax for a variable declaration is as follows:

[attribute_list] [accessibility] [Shared] [Shadows] [ReadOnly] _
Dim [WithEvents] name [(bounds_list)] [As [New] type] _
[= initialization_expression]

All declarations have only one thing in common: They contain a variable's name. Other than the name, different declarations may have nothing in common. Variable declarations with different forms can use or omit any other piece of the general declaration syntax. For example, the following two declarations don't share a single keyword:

Dim i = 1           ' Declare private Integer named i. (Option Explicit Off)
Public j As Integer ' Declare public Integer named j.

The many variations supported by a variable declaration make the general syntax rather intimidating. In most cases, however, declarations are straightforward. The previous two declarations are fairly easy to understand.

The following sections describe the pieces of the general declaration in detail.

Attribute_List

The optional attribute list is a comma-separated list of attributes that apply to the variable. An attribute further refines the definition of a variable to give more information to the compiler and the runtime system.

Attributes are rather specialized and address issues that arise when you perform very specific programming tasks. For example, when you write code to serialize and de-serialize data, you can use serialization attributes to gain more control over the process.

The following code defines the OrderItem class. This class declares three public variables: ItemName, Quantity, and Price. It uses attributes on its three variables to indicate that ItemName should be stored as text, Price should be stored as an attribute named Cost, and Quantity should be stored as an attribute with its default name, Quantity.

Public Class OrderItem
    <XmlText()>
    Public ItemName As String

    <XmlAttributeAttribute(AttributeName:="Cost")>
    Public Price As Decimal

    <XmlAttributeAttribute()>
    Public Quantity As Integer
End Class

The following code shows the XML serialization of an OrderItem object:

<OrderItem Cost="1.25" Quantity="12">Cookie</OrderItem>

Because attributes are so specialized, they are not described in more detail here. For more information, see the sections in the online help related to the tasks you need to perform. For more information on XML serialization attributes, for example, search for "System.Xml.Serialization Namespace," or look at these web pages:

  • XML Serialization in the .NET Framework, msdn.microsoft.com/ms950721.aspx.

  • Controlling XML Serialization Using Attributes, msdn.microsoft.com/2baksw0z(VS.71).aspx.

  • Attributes That Control XML Serialization, msdn.microsoft.com/83y7df3e(VS.71).aspx.

For more information on attributes in general, see the "Attributes" section of the Visual Basic Language Reference or go to msdn.microsoft.com/39967861.aspx.

For a list of attributes you can use to modify variable declarations, search the online help for "Attribute Hierarchy," or see these web pages:

  • Attributes Used in Visual Basic, msdn.microsoft.com/f51fe7sf.aspx.

  • Attribute Class, msdn.microsoft.com/system.attribute.aspx. (Look for the "Inheritance Hierarchy" section to see what attributes inherit from the Attribute class.)

Accessibility

A variable declaration's accessibility clause can take one of the following values:

  • Public — You can use the Public keyword only for variables declared at the module, class, structure, namespace, or file level but not inside a subroutine. Public indicates that the variable should be available to all code inside or outside of the variable's module. This allows the most access to the variable.

  • Protected — You can use the Protected keyword only at the class level, not inside a module or inside a routine within a class. Protected indicates that the variable should be accessible only to code within the same class or a derived class. The variable is available to code in the same or a derived class, even if the instance of the class is different from the one containing the variable. For example, one Employee object can access a Protected variable inside another Employee object.

  • Friend — You can use the Friend keyword only for variables declared at the module, class, namespace, or file level, not inside a subroutine. Friend indicates that the variable should be available to all code inside or outside of the variable's module within the same project. The difference between this and Public is that Public allows code outside of the project to access the variable. This is generally only an issue for code and control libraries. For example, suppose that you build a code library containing dozens of routines and then you write a program that uses the library. If the library declares a variable with the Public keyword, the code in the library and the code in the main program can use the variable. In contrast, if the library declares a variable with the Friend keyword, only the code in the library can access the variable, not the code in the main program.

  • Protected Friend — You can use Protected Friend only at the class level, not inside a module or inside a routine within a class. Protected Friend is the union of the Protected and Friend keywords. A variable declared Protected Friend is accessible only to code within the same class or a derived class and only within the same project.

  • Private — You can use the Private keyword only for variables declared at the module, class, or structure, not inside a subroutine. A variable declared Private is accessible only to code in the same module, class, or structure. If the variable is in a class or structure, it is available to other instances of the class or structure. For example, one Customer object can access a Private variable inside another Customer object.

  • Static — You can use the Static keyword only for variables declared within a subroutine or a block within a subroutine (for example, a For loop or Try Catch block). You cannot use Static with Shared or Shadows. A variable declared Static keeps its value between lifetimes. For example, if a subroutine sets a Static variable to 27 before it exits, the variable begins with the value 27 the next time the subroutine executes. The value is stored in memory, so it is not retained if you exit and restart the whole program. Use a database, the System Registry, or some other means of permanent storage if you need to save values between program runs.

Shared

You can use the Shared keyword at the module, class, structure, namespace, or file level, not within a subroutine. This keyword means that all instances of the class or structure containing the variable share the same variable.

For example, suppose that the Order class declares the Shared variable NumOrders to represent the total number of orders in the application. Then all instances of the Order class share the same NumOrders variable. If one instance of an Order sets NumOrders to 10, all instances of Order see NumOrders equal 10.

You can access a Shared variable either by using a specific class instance or by using the class itself. For example, the following code uses the order1 object's NumOrders variable to set the value of NumOrders to 100. It then displays this value by using order1 and another Order object named order2. Next, it uses the class itself to set the value of NumOrders and uses the class to display the result.

order1.NumOrders = 100            ' Use order1 to set NumOrders = 100.
MessageBox.Show(order1.NumOrders) ' Use order1 to display 100.
MessageBox.Show(order2.NumOrders) ' Use a different Order to Display 100.
Order.NumOrders = 101             ' Use the class to set NumOrders = 101.
MessageBox.Show(Order.NumOrders)  ' Use the class to display 101.

You cannot use the Shared keyword with the Static keyword. This makes sense because a Shared variable is in some fashion static to the class or structure that contains it. If one instance of the class modifies the variable, the value is available to all other instances. In fact, even if you destroy every instance of the class or never create any instances at all, the class itself still keeps the variable's value safe. That provides a persistence similar to that given by the Static keyword.

Shadows

You can use the Shadows keyword only for variables declared at the module, class, structure, namespace, or file level, not inside a subroutine. Shadows indicates that the variable hides a variable with the same name in a base class. In a typical example, a subclass provides a variable with the same name as a variable declared in one of its ancestor classes.

Example program ShadowTest, which is available for download on the book's web site, uses the following code to demonstrate the Shadows keyword:

Public Class Person
    Public LastName As String
    Public EmployeeId As String
End Class

Public Class Employee
    Inherits Person
    Public Shadows EmployeeId As Long
End Class

Public Class Manager
    Inherits Employee
    Public Shadows LastName As String
End Class

Private Sub TestShadows()
    Dim txt As String = ""

    Dim mgr As New Manager
    mgr.LastName = "Manager Last Name"
    mgr.EmployeeId = 1

    Dim emp As Employee = CType(mgr, Employee)
    emp.LastName = "Employee Last Name"
    emp.EmployeeId = 2

    Dim per As Person = CType(mgr, Person)
    per.LastName = "Person Last Name"
    per.EmployeeId = "A"

    txt &= "Manager: " & mgr.EmployeeId & ": " & mgr.LastName & vbCrLf
    txt &= "Employee: " & emp.EmployeeId & ": " & emp.LastName & vbCrLf
    txt &= "Person: " & per.EmployeeId & ": " & per.LastName & vbCrLf

    txtResults.Text = txt
    txtResults.Select(0, 0)
End Sub
                                                  
Shadows

The code defines a Person class that contains public String variables LastName and EmployeeId. The Employee class inherits from Person and declares its own version of the EmployeeId variable. It uses the Shadows keyword so this version covers the version defined by the Person class. Note that Shadows works here even though the two versions of EmployeeId have different data types: Long versus String. An Employee object gets the Long version, and a Person object gets the String version.

The Manager class inherits from the Employee class and defines its own version of the LastName variable. A Manager object uses this version, and an Employee or Person object uses the version defined by the Person class.

Having defined these three classes, the program works with them to demonstrate shadowing. First it creates a Manager object, and sets its LastName variable to "Manager Last Name" and its EmployeeId variable to 1. The LastName value is stored in the Manager class's version of the variable declared with the Shadows keyword. The EmployeeId value is stored in the EmployeeId variable declared with the Shadows keyword in the Employee class.

The program then creates an Employee variable and makes it point to the Manager object. This makes sense because Manager inherits from Employee. A Manager is a type of Employee so an Employee variable can point to a Manager object. The program sets the Employee object's LastName variable to "Employee Last Name" and its EmployeeId variable to 2. The LastName value is stored in the Person class's version of the variable. The EmployeeId value is stored in the EmployeeId variable declared with the Shadows keyword in the Employee class. Because the Manager class does not override this declaration with its own shadowing declaration of EmployeeId, this value overwrites the value stored by the Manager object.

Next, the program creates a Person variable and makes it point to the same Manager object. Again this makes sense because a Manager is a type of Person so a Person variable can point to a Manager object. The program sets the Person object's LastName variable to "Person Last Name" and its EmployeeId variable to "A." The Person class does not inherit, so the program stores the values in the versions of the variables defined by the Person class. Because the Employee class does not override the Person class's declaration of LastName with its own shadowing declaration, this value overwrites the value stored by the Employee object.

Finally, the program prints the values of the EmployeeId and LastName variables for each of the objects.

The following output shows the program's results. Notice that the Employee object's value for EmployeeId (2) overwrote the value saved by the Manager object (1) and that the Person object's value for LastName (Person Last Name) overwrote the value saved by the Employee object (Employee Last Name).

Manager: 2: Manager Last Name
Employee: 2: Person Last Name
Person: A: Person Last Name

Normally, you don't need to access shadowed versions of a variable. If you declare a version of LastName in the Employee class that shadows a declaration in the Person class, you presumably did it for a good reason (unlike in the previous example, which does it just to show how it's done), and you don't need to access the shadowed version directly.

However, if you really do need to access the shadowed version, you can use variables from ancestor classes to do so. For example, the previous example creates Employee and Person objects pointing to a Manager object to access that object's shadowed variables.

Within a class, you can similarly cast the Me object to an ancestor class. For example, the following code in the Manager class makes a Person variable pointing to the same object and sets its LastName value:

Public Sub SetPersonEmployeeId(ByVal employee_id As String)
    Dim per As Person = CType(Me, Person)
    per.EmployeeId = employee_id
End Sub

Code in a class can also use the MyBase keyword to access the variables defined by the parent class. The following code in the Manager class sets the object's LastName variable declared by the Employee parent class:

Public Sub SetManagerLastName(ByVal last_name As String)
    MyBase.LastName = last_name
End Sub

ReadOnly

You can use the ReadOnly keyword only for variables declared at the module, class, structure, namespace, or file level, not inside a subroutine. ReadOnly indicates that the program can read, but not modify, the variable's value.

You can initialize the variable in one of two ways. First, you can include an initialization statement in the variable's declaration, as shown in the following code:

Public Class EmployeeCollection
    Public ReadOnly MaxEmployees As Integer = 100
    ...
End Class

Second, you can initialize the variable in the object's constructors. The following code declares the ReadOnly variable MaxEmployees. The empty constructor sets this variable to 100. A second constructor takes an integer parameter and sets the MaxEmployees to its value.

Public Class EmployeeCollection
    Public ReadOnly MaxEmployees As Integer

    Public Sub New()
        MaxEmployees = 100
    End Sub

    Public Sub New(ByVal max_employees As Integer)
        MaxEmployees = max_employees
    End Sub
    ...
End Class

After the object is initialized, the program cannot modify the ReadOnly variable. This restriction applies to code inside the module that declared the variable, as well as code in other modules. If you want to allow code inside the same module to modify the value but want to prevent code in other modules from modifying the value, you should use a property procedure instead. See the section, "Property Procedures," later in this chapter for more information.

Dim

The Dim keyword officially tells Visual Basic that you want to create a variable.

You can omit the Dim keyword if you specify Public, Protected, Friend, Protected Friend, Private, Static, or ReadOnly. In fact, if you include one of these keywords, the Visual Basic editor automatically removes the Dim keyword if you include it.

If you do not specify otherwise, variables you declare using a Dim statement are Private. The following two statements are equivalent:

Dim num_people As Integer
Private num_people As Integer

One place where the Dim keyword is common is when declaring variables inside subroutines. You cannot use the Private keyword inside a subroutine (or Public, Protected, Friend, Protected Friend, or ReadOnly, for that matter), so you must use either Static or Dim.

WithEvents

The WithEvents keyword tells Visual Basic that the variable is of a specific object type that may raise events that you will want to catch. For example, the following code declares the variable Face as a PictureBox object that may raise events you want to catch:

Private WithEvents Face As PictureBox

When you declare a variable with the WithEvents keyword, Visual Basic creates an entry for it in the left drop-down list in the module's code window, as shown in Figure 15-1.

Visual Basic creates a drop-down entry for variables declared WithEvents.

Figure 15.1. Visual Basic creates a drop-down entry for variables declared WithEvents.

If you select the object in the left drop-down list, Visual Basic fills the right drop-down list with the object's events that you might want to catch, as shown in Figure 15-2.

When you select an object declared WithEvents in the left drop-down list, Visual Basic fills the right drop-down list with events you might want to catch.

Figure 15.2. When you select an object declared WithEvents in the left drop-down list, Visual Basic fills the right drop-down list with events you might want to catch.

If you select an event, Visual Basic creates a corresponding empty event handler. Letting Visual Basic automatically generate the event handler in this way is easier and safer than trying to type the event handler yourself, creating all of the required parameters by hand.

Declaring variables using the WithEvents keyword is a powerful technique. You can make the variable point to an object to catch its events. Later, if you want to process events from some other object using the same event handlers, you can set the variable to point to the new object. If you no longer want to receive any events, you can set the variable to Nothing.

Unfortunately, you cannot declare an array using the WithEvents keyword. That means you cannot use a simple declaration to allow the same event handlers to process events from more than one object. However, you can achieve this by using the AddHandler method to explicitly set the event handler routines for a series of objects. For more information on this technique, see the section "Catching Events" in Chapter 26.

Name

A declaration's name clause gives the name of the variable. This must be a valid Visual Basic identifier. The rules for valid identifiers are a bit confusing, but generally an identifier should begin with a letter or underscore, followed by any number of letters, digits, or underscores.

If the identifier begins with an underscore (which is unusual), it must contain at least one other valid character (letter, digit, or underscore) so that Visual Basic doesn't confuse it with a line continuation character.

Identifier names cannot contain special characters such as &, %, #, and $, although some of these may be used as data type characters.

Here are some examples:

num_employees

Valid

NumEmployees

Valid

_manager

Valid (but unusual)

_

Invalid (contains only a single underscore)

__

Valid (two underscores is valid but could be very confusing)

1st_employee

Invalid (doesn't begin with a letter or underscore)

#employees

Invalid (contains the special character #)

Normal identifiers cannot be the same as a Visual Basic keyword. However, you can escape an identifier (mark it to give it a special meaning) by enclosing it in square brackets. If you escape an identifier, you can give it the same name as a Visual Basic keyword. For example, in the following code, the ParseString subroutine takes a single parameter named String of type String:

Public Sub ParseString(ByVal [String] As String)
    Dim values() As String = Split([String])
    ...
End Sub

If you begin writing a call to this subroutine in the code editor, the IntelliSense pop-up describes this routine as ParseString(String As String).

These rules let you come up with some strange and potentially confusing identifier names. For example, you can make escaped variables named String, Boolean, ElseIf, and Case. Depending on your system's settings, underscores may be hard to read either on the screen or in printouts. That may make variables such as __ (two underscores) seem to vanish and may make it hard to tell the difference between _Name and Name.

Although these identifiers are all legal, they can be extremely confusing and may lead to long, frustrating debugging sessions. To avoid confusion, use escaped identifiers and identifiers beginning with an underscore sparingly.

Bounds_List

A variable declaration's bounds_list clause specifies bounds for an array. This should be a comma-separated list of non-negative integers that give the upper bounds for the array's dimensions. All dimensions have a lower bound of zero. You can optionally specify the lower bound, but it must always be zero.

The following code declares two arrays in two different ways. The first statement declares a one-dimensional array of 101 Customer objects with indexes ranging from 0 to 100. The second statement defines a two-dimensional array of Order objects. The first dimension has bounds ranging from 0 to 100 and the second dimension has bounds ranging from 0 to 10. The array's entries are those between orders(0, 0) and orders(100, 10) giving a total of 101 * 11 = 1111 entries. The last two statements define similar arrays, while explicitly declaring the arrays' lower bounds.

Private customers(100) As Customer
Private orders(100, 10) As Order
Private customers2(0 To 100) As Customer
Private orders2(0 To 100, 0 To 10) As Order

You may find that specifying the lower bound makes the code easier to read because it gives the lower bound explicitly rather than requiring you to remember that lower bounds are always 0. It can be particularly helpful for those who have used Visual Basic 6 and earlier versions because those versions of Visual Basic allowed arrays to have lower bounds other than 0.

Note that declarations of this sort that use an object data type do not instantiate the objects. For example, the first declaration in the previous example defines 101 array entries that all point to Nothing. They do not initially point to instances of the Customer class. After this declaration, the program would need to create each object reference individually, as shown in the following code:

Private customers(100) As Customer
For i As Integer = 0 To 100
    customers(i) = New Customer
Next i

Alternatively, the program can use an initialization statement to declare and initialize the objects in a single step. See the section Initialization_Expression coming up shortly for more information on initializing arrays in their declarations.

If you provide parentheses but no bounds_list, Visual Basic defines the array, but doesn't create it with specific bounds. Later, you can use the ReDim statement to give it bounds. Note that you can also use ReDim to change the bounds of an array that you initially give bounds. The following example declares two arrays named a1 and a2. Initially, the program allocates 11 items for array a1 but no items for array a2. The program then uses ReDim to allocate 21 entries for both arrays.

Dim a1(10) As Integer
Dim a2() As Integer

ReDim a1(20)
ReDim a2(0 To 20)

The ReDim statement cannot change the number of bounds in an array. If you want to declare but not initialize a multidimensional array, include commas as if you were defining the bounds. The following code declares a three-dimensional array and initializes its separate steps:

Dim a1(,,) As Integer

ReDim a1(10, 20, 30)

New

If you are declaring an object variable, the New keyword tells Visual Basic to create a new instance of the object. Without this keyword, Visual Basic makes an object variable that doesn't yet hold a reference to any object. It initially holds Nothing.

For example, the first line in the following code declares an Employee object variable named emp1. After that line, the variable is defined, but it doesn't point to anything. If you examine the variable, you will find that it has the value Nothing. The second line sets emp1 equal to a new Employee object. The last line creates an Employee object variable named emp2 and assigns it to a new Employee object. This does the same thing as the first and second line but in a single statement.

Dim emp1 As Employee
emp1 = New Employee

Dim emp2 As New Manager

If the object's class has constructors that take parameters, you can include the parameters after the class name. For example, suppose that the Employee class has two constructors: an empty constructor (one that takes no parameters) and a constructor that takes first and last name strings as parameters. Then the following code creates two Employee objects using the different constructors:

Dim emp1 As New Employee
Dim emp2 As New Employee("Rod", "Stephens")

Note that you must provide parameters that match some constructor. If the class does not have a constructor that takes no arguments, you cannot use the New keyword without specifying parameters. If the Employee class didn't have an empty constructor, the first line in the previous example would be illegal.

As Type and Inferred Types

The As clause tells Visual Basic what kind of variable you are declaring. For example, the following As statement indicates that the variable cx has type Single:

Dim cx As Single

If Option Infer is on, you do not need to declare a local variable's data type. If you omit the As clause, Visual Basic infers the variable's data type from the value that you assign to it. For example, the following code declares a variable named message. Because the code assigns a string value to the variable, Visual Basic infers that the variable should be a String.

Dim message = "Hello!"

Unfortunately, inferred data types make the code harder to understand later. You can figure out that the previous declaration makes a variable that is a String, but it is much more obvious if you explicitly include the As String clause. In this example, type inference only saves you a few keystrokes and makes the code slightly harder to understand. Now, consider the following statement:

Dim x = 1.234

Does this statement make variable x a Single, Double, Decimal, or some other data type? In this case, it's much less obvious what data type Visual Basic will decide to use. (It makes x a Double.)

The only times when type inference is helpful is when you cannot easily figure out the type needed by a variable. For example, LINQ lets a program generate results that have confusing data types, so type inference can be very handy when working with LINQ. For more information on LINQ, see Chapter 21, "LINQ."

Initialization_Expression

The initialization_expression clause gives data that Visual Basic should use to initialize the variable. The most straightforward form of initialization assigns a simple value to a variable. The following code declares the variable num_employees and assigns it the initial value zero:

Dim num_employees As Integer = 0

More complicated data types may require more complex initialization clauses. If the declaration declares an object variable, you can use the New keyword to initialize the variable. For example, the first line in the following code declares an Employee variable named emp1 and sets it equal to a new Employee object. The second statement uses the As New form of declaration to do the same thing without a separate initialization clause. This version is slightly more compact, but you can use whichever version seems most natural to you.

Dim emp1 As Employee = New Employee("Rod", "Stephens")
Dim emp2 As New Employee("Rod", "Stephens")

The With keyword allows you to initialize an object without using a special constructor. This statement lets you assign values to an object's public properties and variables right after the object is created. The following code creates a new Employee object and sets its FirstName and LastName values much as the previous statements do:

Dim emp3 As New Employee With {.FirstName = "Rod", .LastName = "Stephens"}

Initializing Arrays

Arrays have their own special initialization syntax. To declare and initialize an array in one statement, you must omit the array's bounds. Visual Basic uses the initialization data to discover the bounds.

Place the array's values inside curly braces separated by commas. The following code initializes a one-dimensional array of integers:

Dim fibonacci() As Integer = {1, 1, 2, 3, 5, 8, 13, 21, 33, 54, 87}

If you have Option Infer on, you can omit the array's data type and Visual Basic will try to deduce it from the values that you use to initialize it. For example, the following code creates three arrays. Visual Basic can infer that the first contains Integers and the second contains Strings. The third array contains Strings, Integers, and Doubles so Visual Basic makes it an array of Objects.

Dim numbers() = {1, 2, 3}
Dim strings() = {"A", "B", "C"}
Dim objects() = {"A", 12, 1.23}

This probably makes the most sense if you think of a multidimensional array as an array of arrays. For example, a three-dimensional array is an array of two-dimensional arrays. Each of the two-dimensional arrays is an array of one-dimensional arrays. You can use line continuation and indentation to make the array's structure more obvious.

The following code declares and initializes a two-dimensional array of integers and then prints the values:

Dim int_values(,) As Integer =
{
    {1, 2, 3},
    {4, 5, 6}
}
Dim txt As String = ""
For i As Integer = 0 To 1
    For j As Integer = 0 To 2
        txt &= int_values(i, j)
    Next j
    txt &= vbCrLf
Next i

The following shows this code's output:

123
456

The following code declares and initializes a three-dimensional array of strings. The text for each value gives its position in the array. For example, the value str_values(0, 1, 1) is "011." Notice how the code uses indentation to make the data a bit easier to understand. Items in the first dimension are indented one level and items in the second dimension are indented two levels. The final level is basically a one-dimensional array, which is fairly easy to understand with just commas separating its values. After initializing the array, the code loops through its entries and prints them.

Dim str_values(,,) As String =
{
    {
        {"000", "001", "002"},
        {"010", "011", "012"}
    },
    {
        {"100", "101", "102"},
        {"110", "111", "112"}
    }
}
Dim txt As String = ""
For i As Integer = 0 To 1
    For j As Integer = 0 To 1
        txt &= "[ "
        For k As Integer = 0 To 2
            txt &= str_values(i, j, k) & " "
        Next k
        txt &= "] "
    Next j
    txt &= vbCrLf
Next i

The following text shows this code's output:

[ 000 001 002 ] [ 010 011 012 ]
[ 100 101 102 ] [ 110 111 112 ]

Example program InitializeArrays, which is available for download on the book's web site, uses similar code to demonstrate array initialization.

Note that you must provide the correct number of items for each of the array's dimensions. For example, the following declaration is invalid because the array's second row contains fewer elements than its first row:

Dim int_values(,) As Integer =
{
    {1, 2, 3},
    {4, 5}
}

Initializing Object Arrays

The basic syntax for initializing an array of objects is similar to the syntax you use to initialize any other array. You still omit the array bounds from the declaration and then include values inside curly braces. The values you use to initialize the array, however, are different because object variables do not take simple values such as 12 and "Test" that you would use to initialize integer or string arrays.

If you create an array of objects without an initialization clause, Visual Basic creates the object variables but does not create objects for them. Initially, all of the array's entries are Nothing.

The following code creates an array containing 11 references to Employee objects. Initially, all of the references are set to Nothing.

Dim employees(0 To 10) As Employee

If you want to initialize the objects, you must initialize each object in the array separately using Nothing or the class's constructors. Optionally, you can add a With statement to set public properties and variables after creating the object. The following code declares an array of Employee objects. It initializes two entries using an Employee object constructor that takes as parameters the employees' first and last names, two entries with an empty constructor and a With statement, two with an empty constructor only, and two final entries with the value Nothing.

Dim employees() As Employee =
{
    New Employee("Alice", "Andrews"),
    New Employee("Bart", "Brin"),
    New Employee With {.FirstName = "Cindy", .LastName="Cant"},
    New Employee With {.FirstName = "Dan", .LastName="Diver"},
    New Employee,
    New Employee,
    Nothing,
    Nothing
}

To initialize higher-dimensional arrays of objects, use the syntax described in the previous section. Use Nothing or the New keyword and object constructors to initialize each array entry individually.

Initializing XML Variables

To initialize an XElement object, declare the XElement variable and set it equal to properly formatted XML code. The XML code must begin on the same logical line as the variable assignment, although, as usual, you can use line continuation characters to start the actual XML code on the following line. Visual Basic reads the data's opening tag and then reads XML data until it reaches a corresponding closing tag so the XML data can include whitespace just as an XML document can. In particular, it can span multiple lines without line continuation characters.

For example, the following code declares a variable named book_node that contains XML data representing a book:

Dim book_node As XElement =
    <Book>
        <Title>The Bug That Was</Title>
        <Year>2010</Year>
        <Pages>376</Pages>
    </Book>

This type of declaration and initialization makes it easy to build XML data directly into your Visual Basic applications.

You can initialize XML literal values with much more complicated expressions. For example, you can use LINQ to select values from relational data sources and build results in the form of an XML document. For more information on LINQ, see Chapter 21.

INITIALIZING COLLECTIONS

Starting with Visual Basic 2010, collection classes that provide an Add method such as List, Dictionary, and SortedDictionary have their own initialization syntax. Instead of using an equals sign as you would with an array initializer, use the From keyword followed by the values that should be added to the collection surrounded by curly braces.

For example, the following code initializes a new List(Of String):

Dim pies As New List(Of String) From {
    "Apple", "Banana", "Cherry", "Coconut Cream"
}

The items inside the braces must include all of the values needed by the collection's Add method. For example, the Dictionary class's Add method takes two parameters giving the key and value that should be added.

The following code initializes a Dictionary(Of String, String). The parameters to the class's Add method are an item's key and value so, for example, the value 940-283-1298 has the key Alice Artz.

Later you could look up Alice's phone number by searching the Dictionary for the item with key "Alice Artz."

Dim phone_numbers As New Dictionary(Of String, String) From {
    {"Alice Artz", "940-283-1298"},
    {"Bill Bland", "940-237-3827"},
    {"Carla Careful", "940-237-1983"}
}

Multiple Variable Declarations

Visual Basic .NET allows you to declare more than one variable in a single declaration statement. For example, the following statement declares two Integer variables named num_employees and num_customers:

Private num_employees, num_customers As Integer

You can place accessibility keywords (Private, Public, and so on), Shared, Shadows, and ReadOnly only at the beginning of the declaration and they apply to all of the variables in the declaration. In the preceding statement, both num_employees and num_customers are Private.

You can declare variables with different data types by including more than one As clause separated by commas. The following statement declares two Integer variables and one String variable:

Private emps, custs As Integer, cust As String

You cannot use an initialization statement if multiple variables share the same As clause, but you can include an initialization statement for variables that have their own As clause. In the preceding example, you cannot initialize the two Integer variables, but you can initialize the String variable as shown in the following statement:

Private emps, custs As Integer, cust As String = "Cozmo"

To initialize all three variables, you would need to give them each their own As clauses, as shown in the following example:

Private emps As Integer = 5, custs As Integer = 10, cust As String = "Cozmo"

You can also declare and initialize multiple objects, arrays, and arrays of objects all in the same statement.

Although all of these combinations are legal, they quickly become too confusing to be of much practical use. Even the relatively simple statement that follows can lead to later misunderstandings. Quickly glancing at this statement, the programmer may think that all three variables are declared as Long.

Private num_employees, num_customers As Integer, num_orders As Long

You can reduce the possibility of confusion by using one As clause per declaration. Then a programmer can easily understand how the variables are defined by looking at the beginning and ending of the declaration. The beginning tells the programmer the variables' accessibility and whether they are shared, shadowing other variables, or read-only. The end gives the variables' data type.

You can also keep the code simple by giving variables with initialization statements their own declarations. Then a programmer reading the code won't need to decide whether an initialization statement applies to one or all of the variables.

There's nothing particularly wrong with declaring a series of relatively short variables in a single statement, as long as you don't find the code confusing. The following statements declare five Integer variables and three Single variables. Breaking this into eight separate Dim statements would not make it much clearer.

Dim i, j, k, R, C As Integer
Dim X, Y, Z As Single

OPTION EXPLICIT AND OPTION STRICT

The Option Explicit and Option Strict compiler options play an important role in variable declarations.

When Option Explicit is set to On, you must declare all variables before you use them. If Option Explicit is Off, Visual Basic automatically creates a new variable whenever it sees a variable that it has not yet encountered. For example, the following code doesn't explicitly declare any variables. As it executes the code, Visual Basic sees the first statement, num_managers = 0. It doesn't recognize the variable num_managers, so it creates it. Similarly, it creates the variable i when it sees it in the For loop.

Option Explicit Off
Option Strict Off

Public Class Form1
    ...
    Public Sub CountManagers()
        num_managers = 0
        For i = 0 To m_Employees.GetUpperBound(0)
            If m_Employees(i).IsManager Then num_managrs += 1
        Next i

        MessageBox.Show(num_managers)
    End Sub
    ...
End Class

Keeping Option Explicit turned off can lead to two very bad problems. First, it silently hides typographical errors. If you look closely at the preceding code, you'll see that the statement inside the For loop increments the misspelled variable num_managrs instead of the correctly spelled variable num_managers. Because Option Explicit is off, Visual Basic assumes that you want to use a new variable, so it creates num_managrs. After the loop finishes, the program displays the value of num_managers, which is zero because it was never incremented.

The second problem that occurs when Option Explicit is off is that Visual Basic doesn't really know what you will want to do with the variables it creates for you. It doesn't know whether you will use a variable as an Integer, Double, String, or PictureBox. Even after you assign a value to the variable (say, an Integer), Visual Basic doesn't know whether you will always use the variable as an Integer or whether you might later want to save a String in it.

To keep its options open, Visual Basic creates undeclared variables as generic objects. Then it can fill the variable with just about anything. Unfortunately, this can make the code much less efficient than it needs to be. For example, programs are much better at manipulating integers than they are at manipulating objects. If you are going to use a variable as an integer, creating it as an object makes the program run much slower.

Example program TimeGenericObjects, which is available for download on the book's web site, uses the following code to demonstrate the difference in speed between using variables with explicit types and variables of the generic Object type:

Dim num_trials As Integer = Integer.Parse(txtNumTrials.Text)

Dim start_time As DateTime
Dim stop_time As DateTime
Dim elapsed_time As TimeSpan

start_time = Now
For i As Integer = 1 To num_trials

Next i
stop_time = Now
elapsed_time = stop_time.Subtract(start_time)
lblIntegers.Text = elapsed_time.TotalSeconds.ToString("0.000000")
Refresh()

start_time = Now
For j = 1 To num_trials

Next j
stop_time = Now
elapsed_time = stop_time.Subtract(start_time)
lblObjects.Text = elapsed_time.TotalSeconds.ToString("0.000000")

                                                  
IMPRECISE INFERENCE

The code executes two For loops. In the first loop, it explicitly declares its looping variable to be of type Integer. In the second loop, the code doesn't declare its looping variable, so Visual Basic automatically makes it an Object when it is needed. In one test, the second loop took more than 60 times as long as the first loop.

The second compiler directive that influences variable declaration is Option Strict. When Option Strict is turned off, Visual Basic silently converts values from one data type to another, even if the types are not very compatible. For example, Visual Basic will allow the following code to try to copy the string s into the integer i. If the value in the string happens to be a number (as in the first case), this works. If the string is not a number (as in the second case), this throws an error at runtime.

Dim i As Integer
Dim s As String
s = "10"
i = s       ' This works.
s = "Hello"
i = s       ' This Fails.

If you turn Option Strict on, Visual Basic warns you of possibly illegal conversions at compile time. You can still use conversion functions such as CInt, Int, and Integer.Parse to convert a string into an Integer, but you must take explicit action to do so.

To avoid confusion and ensure total control of your variable declarations, you should always turn on Option Explicit and Option Strict.

For more information on Option Explicit and Option Strict (including instructions for turning these options on), see the "Project" section in Chapter 2, "Menus, Toolbars, and Windows."

SCOPE

A variable's scope tells which other pieces of code can access it. For example, if you declare a variable inside a subroutine, only code within that subroutine can access the variable. The four possible levels of scope are (in increasing size of scope) block, procedure, module, and namespace.

Block Scope

A block is a series of statements enclosed in a construct that ends with some sort of End, Else, Loop, or Next statement. If you declare a variable within a block of code, the variable has block scope, and only other code within that block can access the variable. Furthermore, only code that appears after the variable's declaration can see the variable.

Variables declared in the block's opening statement are also part of the block. Note that a variable is visible within any sub-block contained within the variable's scope.

The following example uses a For loop with the looping variable i declared in the For statement. The scope of variable i is block-defined by the For loop. Code inside the loop can see variable i, but code outside of the loop cannot.

Inside the loop, the code declares variable j. This variable's scope is also the For loop's block.

If i equals j, the program declares variable M and uses it. This variable's scope includes only the two lines between the If and Else statements.

If i doesn't equal j, the code declares variable N. This variable's scope includes only the two lines between the Else and End If statements.

The program then declares variable k. This variable also has block scope, but it is available only after it is declared, so the code could not have accessed it earlier in the For loop.

For i As Integer = 1 To 5
    Dim j As Integer = 3
    If i = j Then
         Dim M As Integer = i + j
         Debug.WriteLine("M: " & M)
    Else
         Dim N As Integer = i * j
         Debug.WriteLine("N: " & N)
    End If

    Dim k As Integer = 123
    Debug.WriteLine("k: " & k)
Next i

Other code constructs that define blocks include the following:

  • Select Case statements — Each Case has its own block.

  • Try Catch statements — The Try section and each Exception statement defines a block. Note also that the exception variable defined in each Exception statement is in its own block; for example, they can all have the same name.

    Try
        Dim i As Integer = CInt("bad value")
    Catch ex As InvalidCastException
        Dim txt As String = "InvalidCastException"
        MessageBox.Show(txt)
    Catch ex As Exception
        Dim txt As String = "Exception"
        MessageBox.Show(txt)
    End Try
  • Single-Line If Then statements — These are strange and confusing enough that you should avoid them, but the following code is legal:

    If manager Then Dim txt As String = "M" : MessageBox.Show(txt) Else _
        Dim txt As String = "E" : MessageBox.Show(txt)
  • While loops — Variables declared inside the loop are local to the loop.

  • Using statements — Resources acquired by the block and variables declared inside the block are local to the block. The Using statement in the following code defines two Employee objects and a Pen object within its block. Those variables are visible only within the block.

    Using _
     emp1 As New Employee("Ann", "Archer"),
     emp2 As New Employee("Bob", "Beagle"),
     the_pen As New Pen(Color.Red)
        ...
    End Using

Because block scope is the most restrictive, you should use it whenever possible to reduce the chances for confusion. The section "Restricting Scope" later in this chapter discusses more about restricting variable scope.

Procedure Scope

If you declare a variable inside a subroutine, function, or other procedure, but not within a block, the variable is visible in any code inside the procedure that follows the declaration. The variable is not visible outside of the procedure. In a sense, the variable has block scope where the block is the procedure.

A procedure's parameters also have procedure scope. For example, in the following code, the scope of the order_object and order_item parameters is the AddOrderItem subroutine:

Public Sub AddOrderItem(ByVal order_object As Order, ByVal order_item As OrderItem)
    order_object.OrderItems.Add(order_item)
End Sub

Module Scope

A variable with module scope is available to all code in its code module, class, or structure, even if the code appears before the variable's declaration. For example, the following code works even though the DisplayLoanAmount subroutine is declared before the m_LoanAmount variable that it displays:

Private Class Lender
    Public Sub DisplayLoanAmount()
        MessageBox.Show(m_LoanAmount)
    End Sub

    Private m_LoanAmount As Decimal
    ...
End Class

To give a variable module scope, you should declare it with the Private, Protected, or Protected Friend keyword. If you declare the variable Private, it is visible only to code within the same module.

If you declare the variable Protected, it is accessible only to code in its class or a derived class. Remember that you can only use the Protected keyword in a class.

A Protected Friend variable is both Protected and Friend. That means it is available only to code that is inside the variable's class or a derived class (Protected), and that is within the same project (Friend).

These keywords apply to both variable and procedure declarations. For example, you can declare a subroutine, function, or property procedure Private, Protected, or Protected Friend.

For more information on accessibility keywords, see the section "Accessibility" earlier in this chapter.

Example program ScopeTest, which is available for download on the book's web site, demonstrates module and procedure scope.

Namespace Scope

By default, a project defines a namespace that includes all the project's variables and code. However, you can use Namespace statements to create other namespaces if you like. This may be useful to help categorize the code in your application.

If you declare a variable with the Public keyword, it has namespace scope and is available to all code in its namespace, whether inside the project or in another project. It is also available to code in any namespaces nested inside the variable's namespace. If you do not create any namespaces of your own, the whole project lies in a single namespace, so you can think of Public variables as having global scope.

If you declare a variable with the Friend keyword, it has namespace scope and is available to all code in its namespace within the same project. It is also available to code in any namespaces nested inside the variable's namespace within the project. If you do not create any namespaces of your own, the whole project lies in a single namespace so you can think of Friend variables as having project scope.

For more information on the Public and Friend keywords, see the section "Accessibility" earlier in this chapter.

Restricting Scope

There are several reasons why you should give variables the most restrictive scope possible that still lets them do their jobs.

Limited scope keeps the variable localized so that programmers cannot use the variable incorrectly in far off code that is unrelated to the variable's main purpose.

Having fewer variables with global scope means programmers have less to remember when they are working on the code. They can concentrate on their current work, rather than worrying about whether variables r and c are declared globally and whether the current code will interfere with them.

Limiting scope keeps variables closer to their declarations, so it's easier for programmers to check the declaration. One of the best examples of this situation is when a For loop declares its looping variable right in the For statement. A programmer can easily see that the looping variable is an integer without scrolling to the top of the subroutine hunting for its declaration. It is also easy to see that the variable has block scope, so other variables with the same names can be used outside of the loop.

Limited scope means a programmer doesn't need to worry about whether a variable's old value will interfere with the current code, or whether the final value after the current code will later interfere with some other code. This is particularly true for looping variables. If a program declares variable i at the top of a subroutine, and then uses it many times in various loops, you might need to do a little thinking to be sure the variable's past values won't interfere with new loops. If you declare i separately in each For statement, each loop has its own version of i, so there's no way they can interfere with each other.

Finally, variables with larger scope tend to be allocated more often, so they take up memory more often. For example, block variables and non-static variables declared with procedure scope are allocated when they are needed and are destroyed when their scope ends, freeing their memory. A variable declared Static or with module or namespace scope is not freed until your application exits. If those variables are large arrays, they may take up a lot of memory the entire time your application is running.

PARAMETER DECLARATIONS

A parameter declaration for a subroutine, function, or property procedure defines the names and types of the parameters passed into it. Parameter declarations always have non-static procedure scope. Visual Basic creates parameter variables when a procedure begins and destroys them when the procedure ends. The subroutine's code can access the parameters, but code outside of the routine cannot.

For example, the following subroutine takes an integer as a parameter. The subroutine calls this value employee_id. Code within the subroutine can access employee_id, whereas code outside of the subroutine cannot.

Public Sub DisplayEmployee(ByVal employee_id As Integer)
    ...
End Sub

Whereas a parameter's basic scope is straightforward (non-static procedure scope), parameters have some special features that complicate the situation. Although this isn't exactly a scoping issue, it's related closely enough to scope that it's worth covering here.

You can declare a parameter ByRef or ByVal (ByVal is the default if you use neither keyword). If you declare the variable ByVal, the routine makes its own local parameter variable with procedure scope just as you would expect.

If you declare a parameter with the ByRef keyword, the routine does not create a separate copy of the parameter variable. Instead, it uses a reference to the parameter you pass in, and any changes the routine makes to the value are reflected in the calling subroutine.

For example, the following code includes two routines that double their parameters. Subroutine DoubleItByVal declares its parameter with the ByVal keyword. This routine makes a new variable named X and copies the value of its parameter into that variable. The parameter X is available within the subroutine, the routine multiplies it by 2, and then exits. At that point, the parameter variable goes out of scope and is destroyed.

Subroutine DoubleItByRef declares its parameter with the ByRef keyword. This routine's variable X is a reference to the variable passed into the routine. The subroutine doubles X and that doubles the variable in the calling code.

Subroutine TestParameters calls each of these routines. It declares a variable named value, passes it to subroutine DoubleItByVal, and displays the result after DoubleItByVal returns. Because DoubleItByVal declares its parameter ByVal, the variable value is not changed so the result is 10.

Subroutine TestParameters then calls subroutine DoubleItByRef and displays the result after the call returns. Subroutine DoubleItByRef declares its parameter ByRef so the variable value is changed to 20.

Sub DoubleItByVal(ByVal X As Single)
    X*= 2
End Sub
Sub DoubleItByRef(ByRef X As Single)
    X*= 2
End Sub
Sub TestParameters()
    Dim value As Single

    value = 10
    DoubleItByVal(value)
    Debug.WriteLine(value)

    value = 10
    DoubleItByRef(value)
    Debug.WriteLine(value)
End Sub

Even this more complex view of how procedures handle parameters has exceptions. If you pass a literal value or the result of an expression into a procedure, there is no variable to pass by reference, so Visual Basic must create its own temporary variable. In that case, any changes made to a ByRef parameter are not returned to the calling routine, because that code did not pass a variable into the procedure. The following code shows statements that pass a literal expression and the result of an expression into the DoubleItByRef subroutine:

DoubleItByRef(12)    ' Literal expression.
DoubleItByRef(X + Y) ' Result of an expression.

Another case where a ByRef parameter does not modify a variable in the calling code is when you omit an optional parameter. For example, the following subroutine takes an optional ByRef parameter. If you call this routine and omit the parameter, Visual Basic creates the employee_id parameter from scratch so the subroutine can use it in its calculations. Because you called the routine without passing it a variable, the subroutine does not update a variable.

Sub UpdateEmployee(Optional ByRef employee_id As Integer = 0)
   ...
End Sub

Probably the sneakiest way a ByRef variable can fail to update a variable in the calling routine is if you enclose the variable in parentheses. The parentheses tell Visual Basic to evaluate their contents as an expression, so Visual Basic creates a temporary variable to hold the result of the expression. It then passes the temporary variable into the procedure. If the procedure's parameter is declared ByRef, it updates the temporary variable, but not the original variable, so the calling routine doesn't see any change to its value.

The following code calls subroutine DoubleItByRef, passing the variable value into the routine surrounded with parentheses. The DoubleItByRef subroutine doubles the temporary variable Visual Basic creates, leaving value unchanged.

DoubleItByRef((value))

Keep these issues in mind when you work with parameters. Parameters have non-static procedure scope but the ByRef keyword can sometimes carry their values outside of the routine.

For more information on routines and their parameters, see Chapter 17.

PROPERTY PROCEDURES

Property procedures are routines that can represent a variable-like value. To other pieces of the program, property procedures look just like variables, so they deserve mention in this chapter.

The following code shows property procedures that implement a Name property. The Property Get procedure simply returns the value in the private variable m_Name. The Property Set procedure saves a new value in the m_Name variable.

Private m_Name As String

Property Name() As String
    Get
        Return m_Name
    End Get
    Set(ByVal Value As String)
        m_Name = Value
    End Set
End Property

A program could use these procedures exactly as if there were a single public Name variable. For example, if this code is in the Employee class, the following code shows how a program could set and then get the Name value for the Employee object named emp:

emp.Name = "Rod Stephens"
MessageBox.Show(emp.Name)

You might want to use property procedures rather than a public variable for several reasons. First, the routines give you extra control over the getting and setting of the value. For example, you could use code to validate the value before saving it in the variable. The code could verify that a postal code or phone number has the proper format and throw an error if the value is badly formatted.

You can set breakpoints in property procedures. Suppose that your program is crashing because a piece of code is setting an incorrect value in a variable. If you implement the variable with property procedures, you can set a breakpoint in the Property Set procedure and stop whenever the program sets the value. This can help you find the problem relatively quickly.

Property procedures let you set and get values in formats other than those you want to actually use to store the value. For example, the following code defines Name property procedures that save a name in m_FirstName and m_LastName variables. If your code would often need to use the last and first names separately, you could also provide property procedures to give access to those values separately.

Private m_LastName As String
Private m_FirstName As String

Property MyName() As String
    Get
        Return m_FirstName & " " & m_LastName
    End Get
    Set(ByVal Value As String)
        m_FirstName = Value.Split(" "c)(0)
        m_LastName = Value.Split(" "c)(1)
    End Set
End Property

Finally, you can use property procedures to create read-only and write-only variables. The following code shows how to make a read-only NumEmployees property procedure and a write-only NumCustomers property procedure. (Write-only property procedures are unusual but legal.)

Public ReadOnly Property NumEmployees() As Integer
    Get
         ...
    End Get
End Property

Public WriteOnly Property NumCustomers() As Integer
    Set(ByVal Value As Integer)
        ...
    End Set
End Property

You don't need to remember all of the syntax for property procedures. If you type the first line and press Enter, Visual Basic fills in the rest of the empty property procedures. If you use the keyword ReadOnly or WriteOnly, Visual Basic only includes the appropriate procedure.

Visual Basic 2010 introduced auto-implemented properties. These are simple properties that do not have separate property procedures. You declare the property's name and Visual Basic automatically creates the necessary backing variables and property procedures behind the scenes.

The following code shows a simple FirstName property:

Public Property FirstName As String

You can give a property a default value as in the following code:

Public Property FirstName As String = "<missing>"

You cannot use the ReadOnly or WriteOnly keywords with auto-implemented properties. If you want to make a read-only or write-only property, you need to write Get and Set procedures as described earlier.

The advantage of auto-implemented properties is that you don't need to write as much code. The disadvantage is that you can't set breakpoints in the property procedures.

ENUMERATED DATA TYPES

An enumerated type is a discrete list of specific values. You define the enumerated type and the values allowed. Later, if you declare a variable of that data type, it can take only those values.

For example, suppose that you are building a large application where users can have one of three access levels: clerk, supervisor, and administrator. You could define an enumerated type named AccessLevel that allows the values Clerk, Supervisor, and Administrator. Now, if you declare a variable to be of type AccessLevel, Visual Basic will only allow the variable to take those values.

The following code shows a simple example. It defines the AccessLevel type and declares the variable m_AccessLevel using the type. Later the MakeSupervisor subroutine sets m_AccessLevel to the value AccessLevel.Supervisor. Note that the value is prefixed with the enumerated type's name.

Public Enum AccessLevel
    Clerk
    Supervisor
    Administrator
End Enum

Private m_AccessLevel As AccessLevel ' The user's access level.

' Set supervisor access level.
Public Sub MakeSupervisor()
    m_AccessLevel = AccessLevel.Supervisor
End Sub

The syntax for declaring an enumerated type is as follows:

[attribute_list] [accessibility] [Shadows] Enum name [As type]
    [attribute_list] value_name [= initialization_expression]
    [attribute_list] value_name [= initialization_expression]
   ...
End Enum

Most of these terms, including attribute_list and accessibility, are similar to those used by variable declarations. See the section "Variable Declarations" earlier in this chapter for more information.

The type value must be an integral type and can be Byte, Short, Integer, or Long. If you omit this value, Visual Basic stores the enumerated type values as integers.

The value_name pieces are the names you want to allow the enumerated type to have. You can include an initialization_expression for each value if you like. This value must be compatible with the underlying data type (Byte, Short, Integer, or Long). If you omit a value's initialization expression, the value is set to one greater than the previous value. The first value is zero by default.

In the previous example, Clerk = 0, Supervisor = 1, and Administrator = 2. The following code changes the default assignments so Clerk = 10, Supervisor = 11, and Administrator = −1:

Public Enum AccessLevel
    Clerk = 10
    Supervisor
    Administrator = −1
End Enum

Usually, all that's important about an enumerated type is that its values are unique, so you don't need to explicitly initialize the values.

Note that you can give enumerated values the same integer value either explicitly or implicitly. For example, the following code defines several equivalent AccessLevel values. The first three values, Clerk, Supervisor, and Administrator, default to 0, 1, and 2, respectively. The code explicitly sets User to 0, so it is the same as Clerk. The values Manager and SysAdmin then default to the next two values, 1 and 2 (the same as Supervisor and Administrator, respectively). Finally, the code explicitly sets Superuser = SysAdmin.

Public Enum AccessLevel
    Clerk
    Supervisor
    Administrator
    User = 0
    Manager
    SysAdmin
    Superuser = SysAdmin
End Enum

This code is somewhat confusing. The following version makes it more obvious that some values are synonyms for others:

Public Enum AccessLevel
    Clerk
    Supervisor
    Administrator

    User = Clerk
    Manager = Supervisor
    SysAdmin = Administrator
    Superuser = Administrator
End Enum
                                                  
ENUMERATED DATA TYPES

You can get an effect similar to enumerated types using integer variables and constants, as shown in the following code. This code does roughly the same thing as the previous examples.

Public Const Clerk As Integer = 0
Public Const Supervisor As Integer = 1
Public Const Administrator As Intger = 2

Private m_AccessLevel As Integer ' The user's access level.

' Set supervisor access level.
Public Sub MakeSupervisor()
    m_AccessLevel = Supervisor
End Sub

Declaring an enumerated type has a couple of advantages over using integers and constants, however. First, it prevents you from assigning nonsense values to the variable. In the previous code, you could set m_AccessLevel to 10, which wouldn't make any sense.

Using an enumerated data type allows Visual Basic to verify that the value you are assigning to the variable makes sense. You can only set the variable equal to one of the values in the enumerated type or to the value stored in another variable of the same enumerated type.

If you really need to set an enumerated variable to a calculated value for some reason, you can use the CType function to convert an integer value into the enumerated type. For example, the following statement uses the value in the variable integer_value to set the value of the variable m_AccessLevel. Making you use CType to perform this type of conversion makes it less likely that you will set an enumerated value accidentally.

m_AccessLevel = CType(integer_value, AccessLevel)

Another benefit of enumerated types is that they allow Visual Basic to provide IntelliSense help. If you type m_AccessLevel =, Visual Basic provides a list of the allowed AccessLevel values.

A final benefit of enumerated types is that they provide a ToString method that returns the textual name of the value. For example, the following code displays the message "Clerk":

Dim access_level As AccessLevel = Clerk
MessageBox.Show(access_level.ToString())

Example program AccessLevelEnum, which is available for download on the book's web site, makes an AccessLevel Enum and then displays the results returned by calling ToString for each of its values.

If you have a value that can take only a fixed number of values, you should probably make it an enumerated type. Also, if you discover that you have defined a series of constants to represent related values, you should consider converting them into an enumerated type. Then you can gain the benefits of the improved Visual Basic type checking and IntelliSense.

ANONYMOUS TYPES

An anonymous type is an object data type that is built automatically by Visual Basic and never given a name for the program to use. The type is used implicitly by the code that needs it and is then discarded.

The following code uses LINQ to select data from an array of BookInfo objects named m_BookInfo. It begins by using a LINQ query to fill variable book_query with the selected books. It then iterates through the results stored in book_query, adding information about the selected books to a string. It finishes by displaying the string in a text box.

Dim book_query =
    From book In m_BookInfo
    Where book.Year > 1999
    Select book.Title, book.Pages, book.Year
    Order By Year

Dim txt As String = ""
For Each book In book_query
    txt &= book.Title & " (" & book.Year & ", " &
        book.Pages & " pages)" & ControlChars.CrLf
Next book
txtResult.Text = txt

The book_query variable is an ordered sequence containing objects that hold the data selected by the query: Title, Pages, and Year. This type of object doesn't have an explicit definition; it is an anonymous type created by Visual Basic to hold the selected values Title, Pages, and Year. If you hover the mouse over the book_query variable in the code editor, a tooltip appears giving the variable's data type as:

System.Linq.IOrderedSequence(Of <anonymous type>)

Later, the code uses a For Each loop to enumerate the objects in book_query. The looping variable book must have the same type as the items in the sequence. The code does not explicitly give the variable's data type, so Visual Basic can infer it. If you hover the mouse over the book variable in the code editor, a tooltip appears giving the variable's data type as:

<anonymous type>

You are not really intended to use anonymous types explicitly. For example, you shouldn't need to declare a new object of the anonymous type. They are intended to support LINQ. Although you won't use anonymous types explicitly, it's still helpful to understand what they are.

For more information on LINQ, see Chapter 21.

NULLABLE TYPES

Most relational databases have a concept of a null data value. A null value indicates that a field does not contain any data. It lets the database distinguish between valid zero or blank values and non-existing values. For example, a null value in a text field means there is no data in the field and a blank value means the field contains a value that happens to be blank.

You can create a nullable variable in Visual Basic by adding a question mark either to the variable's name or after its data type. You can also declare the variable to be of type Nullable(Of type). For example, the following code declares three nullable integers:

Dim i As Integer?
Dim j? As Integer
Dim k As Nullable(Of Integer)

To make a nullable variable "null," set it equal to Nothing. The following code makes variable num_choices null:

num_choices = Nothing

To see if a nullable variable contains a value, use the Is operator to compare it to Nothing. The following code determines whether the nullable variable num_choices contains a value. If the variable contains a value, the code increments it. Otherwise the code sets the value to 1.

If num_choices IsNot Nothing Then
    num_choices += 1
Else
    num_choices = 1
End If

Calculations with nullable variables use "null-propagation" rules to ensure that the result makes sense. For example, if a nullable integer contains no value, it probably doesn't make sense to add another number to it. (What is null plus three?)

If one or more operands in an expression contains a null value, the result is a null value. For example, if num_choices in the previous example contains a null value, then num_choices + 1 is also a null value. (That's why the previous code checks explicitly to see whether num_choices is null before incrementing its value.)

Example program NullableTypes, which is available for download on the book's web site, demonstrates nullable types.

CONSTANTS

In many respects, a constant is a lot like a read-only variable. Both variable and constant declarations may have attributes, accessibility keywords, and initialization expressions. Both read-only variables and constants represent a value that the code cannot change after it is assigned.

The syntax for declaring a constant is as follows:

[attribute_list] [accessibility] [Shadows] _
Const name [As type] = initialization_expression

For the general meanings of the various parts of a constant declaration, see the section "Variable Declarations" earlier in this chapter. The following sections describe differences between read-only variable and constant declarations.

Accessibility

When you declare a variable, you can omit the Dim keyword if you use any of the keywords Public, Protected, Friend, Protected Friend, Private, Static, or ReadOnly. You cannot omit the Const keyword when you declare a constant, because it tells Visual Basic that you are declaring a constant rather than a variable.

You cannot use the Static, ReadOnly, or Shared keywords in a constant declaration. Static implies that the value will change over time, and the value should be retained when the enclosing routine starts and stops. Because the code cannot change a constant's value, that doesn't make sense.

The ReadOnly keyword would be redundant because you already cannot change a constant's value.

You use the Shared keyword in a variable declaration within a class to indicate that the variable's value is shared by all instances of the class. If one object changes the value, all objects see the changed value. Because the program cannot change a constant's value, the value need not be shared. All objects have the same version of the constant at all times. You can think of a constant as always shared.

You can use the other accessibility keywords in a constant declaration: Public, Protected, Friend, Protected Friend, and Private.

As Type

If you have Option Strict turned on, you must include the constant's data type. A constant can only be an intrinsic type (Boolean, Byte, Short, Integer, Long, Decimal, Single, Double, Char, String, Date, or Object) or the name of an enumerated type. You cannot declare a constant that is a class, structure, or array.

If you declare the constant with the Object data type, the initialization_expression must set the object equal to Nothing. If you want a constant that represents some other object, or a class, structure, or array, use a read-only variable instead.

Because the generic Object class doesn't raise any events, and because you cannot make a constant of some other class type, it doesn't make sense to use the WithEvents keyword in a constant declaration.

Initialization_Expression

The initialization_expression assigns the constant its never-changing value. You cannot use variables in the initialization_expression, but you can use conversion functions such as CInt. You can also use the values of previously defined constants and enumeration values. The expression can include type characters such as # or &H, and if the declaration doesn't include a type statement (and Option Explicit is off), the type of the value determines the type of the constant.

The following code demonstrates these capabilities. The first statement uses the CInt function to convert the value 123.45 into an integer constant. The second and third statements set the values of two Long constants to hexadecimal values. The next statement combines the values defined in the previous two using a bitwise Or. The final statement sets a constant to a value defined by the enumerated type AccessLevel.

Private Const MAX_VALUES As Integer = CInt(123.45)
Private Const MASK_READ As Long = &H1000&
Private Const MASK_WRITE As Long = &H2000&
Private Const MASK_READ_WRITE As Long = MASK_READ Or MASK_WRITE
Private Const MAX_ACCESS_LEVEL As AccessLevel = AccessLevel.SuperUser

DELEGATES

A delegate is an object that refers to a subroutine, function, or other method. The method can be an instance method provided by an object, a class's shared method, or a method defined in a code module. A delegate variable acts as a pointer to a subroutine or function. Delegate variables are sometimes called type-safe function pointers.

The Delegate keyword defines a delegate class and specifies the parameters and return type of the method to which the delegate will refer.

The following code uses a Delegate statement to declare the StringDisplayerType to be a delegate to a subroutine that takes a string as a parameter. Next, the code declares the variable m_DisplayStringRoutine to be of this type. This variable can hold a reference to a subroutine that takes a string parameter. The code then sets the variable equal to the ShowStringInOutputWindow subroutine. Finally, the code invokes the delegate's subroutine, passing it a string.

' Define a StringDisplayerType delegate to be a pointer to a subroutine
' that has a string parameter.
Private Delegate Sub StringDisplayerType(ByVal str As String)

' Declare a StringDisplayerType variable.
Dim m_DisplayStringRoutine As StringDisplayerType

' Assign the variable to a subroutine.
m_DisplayStringRoutine = AddressOf ShowStringInOutputWindow

' Invoke the delegate's subroutine.
m_DisplayStringRoutine("Hello world")

The delegate in the preceding example holds a reference to a subroutine defined in a code module. A delegate can also hold the address of a class's shared method or an instance method. For example, suppose the Employee class defines the shared function GetNumEmployees that returns the number of employees loaded. Suppose that it also defines the instance function ToString that returns an Employee object's first and last names.

Example program UseDelegates, which is available for download on the book's web site, uses the following code to demonstrate delegates for both of these functions:

Dim emp As New Employee("Rod", "Stephens")

' Use a delegate pointing to a shared class method.
Private Delegate Function NumEmployeesDelegate() As Integer

Private Sub btnShared_Click() Handles btnShared.Click
    Dim show_num As NumEmployeesDelegate
    show_num = AddressOf Employee.GetNumEmployees
    MessageBox.Show(show_num().ToString, "# Employees")
End Sub

' Use a delegate pointing to a class instance method.
Private Delegate Function GetNameDelegate() As String
Private Sub btnInstance_Click() Handles btnInstance.Click
    Dim show_name As GetNameDelegate
    show_name = AddressOf emp.ToString
    MessageBox.Show(show_name(), "Name")
End Sub
                                                  
DELEGATES

First, it declares and initializes an Employee object named emp. It then defines a delegate named NumEmployeesDelegate, which is a pointer to a function that returns an integer. The btnShared_Click event handler declares a variable of this type, sets it to the address of the Employee class's shared GetNumEmployees function, and calls the function. Then the code defines a delegate named GetNameDelegate, which is a pointer to a function that returns a string. The btnInstance_Click event handler declares a variable of this type, sets it to the address of the emp object's ToString function, and then calls the function.

These examples are somewhat contrived because the code could easily invoke the subroutines and functions directly without delegates, but they show how a program can save a delegate pointing to a subroutine or function and then call it later. A real application might set the delegate variable's value and only use it much later.

A particular delegate variable could hold references to different methods, depending on the program's situation. For example, different subroutines might generate output on a form, on the printer, or into a bitmap file. The program could set a delegate variable to any of these routines. Later, the program could invoke the variable's routine without needing to know which routine will actually execute.

Another useful technique is to pass a delegate variable into a subroutine or function. For example, suppose that you are writing a subroutine that sorts an array of Customer objects. This routine could take as a parameter a delegate variable that references the function to use when comparing the objects in the array. By passing different functions into the routine, you could make it sort customers by company name, contact name, customer ID, total past sales, or anything else you can imagine.

Delegates are particularly confusing to many programmers, but understanding them is worth a little extra effort. They can add an extra dimension to your programming by essentially allowing you to manipulate subroutines and functions as if they were data.

NAMING CONVENTIONS

Many development teams adopt naming conventions to make their code more consistent and easier to read. Different groups have developed their own conventions, and you cannot really say that one of them is best. It doesn't really matter which convention you adopt. What is important is that you develop some coding style that you use consistently.

One rather simple convention is to use lowercase_letters_with_underscores for variables with routine scope, MixedCaseLetters for variables with module and global scope, and ALL_CAPS for constants of any scope. Use the prefixes m_ and g_ to differentiate between module and global scope, and an abbreviation to give an object's data type. For example, the following statement defines a module-scope PictureBox variable:

Private m_picCanvas As PictureBox

Routine names are generally MixedCase.

Many developers carry these rules a bit further and add type prefix abbreviations to all variables, not just objects. For example, this statement declares an integer variable:

Dim iNumEmployees As Integer

If you apply these rules strictly enough, you should never need to assign one variable to another variable's value, unless the two have the same type abbreviation. If you see a statement that mixes variable types, you should examine the code more closely to see if there is a real data type mismatch problem. For example, the following statement should make the developer suspicious because it's assigning an Integer value to a Long variable:

mlngNumEmployees = intNumAbsent + intNumPresent

Some developers extend the rules to cover all programming objects, including functions and subroutines. For example, a global function that returns a string might be named gstrGetWebmasterName.

Generally, this scope and type information is more important the farther you are from a variable's declaration. If you declare a variable inside a subroutine, a developer can usually remember the variable's data type. If there is any doubt, it's easy to scroll up and review the variable's declaration.

In contrast, if a variable is declared globally in an obscure code module that developers rarely need to read, a programmer may have trouble remembering the variable's scope and data type. In that case, using prefixes to help the developers' memory can be important.

No matter which convention you use, the most important piece of a name is the descriptive part. The name mblnDL tells you that the value is a module-scope Boolean, but it doesn't tell you what the value means (and variables with such terrible names are all too common). The name mblnDataIsLoaded is much more descriptive.

Building an all-encompassing naming convention that defines abbreviations for every conceivable type of data, control, object, database component, menu, constant, and routine name takes a lot of time and more space than it's worth in a book such as this. For an article that describes the conventions used by Microsoft Consulting Services, go to support.microsoft.com/kb/110264. It explains everything, including data type abbreviations, making the first part of a function name contain a verb (GetUserName rather than UserName), and commenting conventions.

Naming and coding conventions make it easier for other programmers to read your code. Look over the Microsoft Consulting Services conventions or search the Web for others. Select the features that you think make the most sense and ignore the others. It's more important that you write consistent code than that you follow a particular set of rules.

SUMMARY

Two of the most important things you control with a variable declaration are its data type and its visibility. Visibility combines scope (the piece of code that contains the variable such as a For loop, subroutine, or module), accessibility (the code that is allowed to access the variable determined by keywords such as Private, Public, and Friend), and lifetime (when the variable has been created and not yet destroyed).

To avoid confusion, explicitly declare the data type whenever possible and use the most limited scope possible for the variable's purpose. Turn Option Explicit and Option Strict on to allow the IDE to help you spot potential scope and type errors before they become a problem.

Code that uses LINQ complicates matters somewhat. When you use LINQ, it is generally not possible to declare explicitly every variable's data type. A LINQ query returns a sequence of objects that have an anonymous type. If you enumerate over the sequence, the looping variable will be of the same anonymous type. In those cases, when you cannot explicitly declare a variable's type, use extra caution to make the code easy to understand so you can fix and maintain it later. For more information on LINQ, see Chapter 21.

Parameters, property procedures, and constants have similar data types and scope issues. Once you become comfortable with variable declarations, they should give you little trouble.

One of the most important steps you can take to make your code easier to debug and maintain is to make your code consistent. A good naming convention can help. Review the guidelines used by Microsoft Consulting Services, and adopt the pieces that make the most sense to you.

When you know how to declare variables, you are ready to learn how to combine them. Chapter 16, "Operators," explains the symbols (such as +, 2, and ^) that you can use to combine variables to produce new results.

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

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