Chapter 18. Program Control Statements

Program control statements tell an application which other statements to execute under a particular set of circumstances. They control the path that execution takes through the code. They include commands that tell the program to execute some statements but not others and to execute certain statements repeatedly.

The two main categories of control statements are decision statements (or conditional statements) and looping statements. The following sections describe in detail the decision and looping statements provided by Visual Basic .NET.

DECISION STATEMENTS

A decision or conditional statement represents a branch in the program. It marks a place where the program can execute one set of statements or another, or possibly no statements at all, depending on some condition. These include If, Choose, and Select Case statements.

Single-Line If Then

The single-line If Then statement has two basic forms. The first allows the program to execute a single statement if some condition is true. The syntax is as follows:

If condition Then statement

If the condition is true, the program executes the statement. In the most common form of single-line If Then statement, the statement is a single simple command (such as assigning a value to a variable or calling a subroutine).

The following example checks the emp object's IsManager property. If IsManager is True, the statement sets the emp object's Salary property to 90,000.

If emp.IsManager Then emp.Salary = 90000

The second form of the single-line If Then statement is more confusing and generally harder to debug and maintain. To prevent unnecessary confusion, many programmers switch to the multiline If Then statement described in the next section when the simple single-statement version won't work.

The second form of single-line If Then statement uses the Else keyword. The syntax is as follows:

If condition Then statement1 Else statement2

If the condition is true, the code executes the first statement. If the condition is false, the code executes the second statement. The decision about which statement to execute is an either-or decision; the code executes one statement or the other, but not both.

This type of single-line If Then Else statement can be confusing if it is too long to easily see in the code editor. For longer statements, a multiline If Then Else statement is easier to understand and debug. The performance of single-line and multiline If Then Else statements is comparable (in one test, the multiline version took only about 80 percent as long), so you should use the one that is easiest for you to read.

The statements executed by a single-line If Then statement can be simple commands (such as assigning a value to a variable). They can also be a series of simple statements separated by colons on the same line. For example, the following code tests the value of the Boolean variable is_new_customer. If is_new_customer is true, the program calls the customer object's Initialize method and then calls its Welcome method.

If is_new_customer Then customer.Initialize(): customer.Welcome()

Using more than one simple statement separated by colons like this can be perplexing. It gets even worse if you use single-line If Then Else, as shown here:

If order.Valid() Then order.Save(): order.Post() Else order.order.Delete()

The single-line If Then statement can also include Else If clauses. For example, the following code examines the variable X. If X is 1, the program sets variable txt to "One." If X has the value 2, the program sets txt to "Two." If X is not 1 or 2, the program sets txt to a question mark.

Dim txt As String
If X = 1 Then txt = "One" Else If X = 2 Then txt = "Two" Else txt = "?"

The code can include as many Else If clauses as you like, and each execution statement can be composed of multiple simple statements separated by colons. However, confusing code such as these examples can lead to puzzling bugs that are easy to avoid if you use multiline If Then statements instead.

In summary, if you can write a simple single-line If Then statement with no Else If or Else clauses, and the whole thing fits nicely on the line so that it's easy to see the whole thing without confusion, go ahead. If the statement is too long to read easily, contains Else If or Else clauses, or executes a series of statements separated by colons, you are usually better off using a multiline If Then statement. It may take more lines of code, but the code will be easier to read, debug, and maintain later.

Multiline If Then

A multiline If Then statement can execute more than one line of code when a condition is true. The syntax for the simplest form of multiline If Then statement is as follows:

If condition Then
    statements ...
End If

If the condition is true, the program executes all the commands that come before the End If statement.

Like the single-line If Then statement, the multiline version can include Else If and Else clauses. For possibly historical reasons, ElseIf is spelled as a single word in the multiline If Then statement. The syntax is as follows:

If condition1 Then
    statements1 ...
ElseIf condition2
    statements2 ...
Else
    statements3 ...
End If

If the first condition is true, the program executes the first set of statements. If the first condition is false, the code examines the second condition and, if that one is true, the code executes the second set of statements. The program continues checking conditions until it finds one that is true and it executes the corresponding code.

If the program reaches an Else statement, it executes the corresponding code. If the program reaches the End If statement without finding a true condition or an Else clause, it doesn't execute any of the statement blocks.

It is important to understand that the program exits the If Then construction immediately after it has executed any block of statements. It does not examine the other conditions. This saves the program some time and is particularly important if the conditions involve functions. If each test calls a relatively slow function, skipping these later tests can save the program a significant amount of time.

Select Case

The Select Case statement lets a program execute one of several pieces of code depending on a single value. The basic syntax is as follows:

Select Case test_value
    Case comparison_expression1
        statements1
    Case comparison_expression2
        statements2
    Case comparison_expression3
        statements3
    ...
    Case Else
        else_statements
End Select

If test_value matches comparison_expression1, the program executes the statements in the block statements1. If test_value matches comparison_expression2, the program executes the statements in the block statements2. The program continues checking the expressions in the Case statements in order until it matches one, or it runs out of Case statements.

If test_value doesn't match any of the expressions in the Case statements, the program executes the code in the else_statements block. Note that you can omit the Case Else section. In that case, the program executes no code if test_value doesn't match any of the expressions.

Select Case is functionally equivalent to an If Then Else statement. The following code does the same thing as the previous Select Case code:

If test_value = comparison_expression1 Then
    statements1
ElseIf test_value = comparison_expression2 Then
    statements2
ElseIf test_value = comparison_expression3 Then
    statements3
...
Else
    else_statements
End If

Select Case is sometimes easier to understand than a long If Then Else statement. It is often faster as well, largely because Select Case doesn't need to reevaluate test_value for every Case statement. If test_value is a simple variable, the difference is insignificant, but if test_value represents a slow function call, the difference can be important. For example, suppose test_value represents a function that opens a database and looks up a value. The Select Case version will find the value once and use it in each comparison, whereas the If Then version would reopen the database for each comparison.

The previous If Then example assumes the comparison expressions are constants. A comparison expression can also specify ranges using the To and Is keywords, and include a comma-separated list of expressions. These forms are described in the following sections. The final section describing Select Case discusses using enumerated values to control Select Case statements.

To

The To keyword specifies a range of values that test_value should match. The following code examines the variable num_items. If num_items is between 1 and 10, the program calls subroutine ProcessSmallOrder. If num_items is between 11 and 100, the program calls subroutine ProcessLargeOrder. If num_items is less than 1 or greater than 100, the program beeps.

Select Case num_items
    Case 1 To 10
        ProcessSmallOrder()
    Case 11 To 100
        ProcessLargeOrder()
    Case Else
        Beep()
End Select

Is

The Is keyword lets you perform logical comparisons using the test value. The word Is takes the place of the test value in the comparison expression. For example, the following code does almost the same things as the previous version. If the value num_items is less than or equal to 10, the program calls subroutine ProcessSmallOrder. If the first Case clause doesn't apply and num_items is less than or equal to 100, the program calls subroutine ProcessLargeOrder. If neither of these cases applies, the program beeps.

Select Case num_items
    Case Is <= 10
        ProcessSmallOrder()
    Case Is <= 100
        ProcessLargeOrder()
    Case Else
        Beep()
End Select

This version is slightly different from the previous one. If num_items is less than 1, this code calls subroutine ProcessSmallOrder whereas the previous version beeps.

You can use the operators =, <>, <, <=, >, and >= in an Is clause. (In fact, if you use a simple value in a Case clause as in Case 7, you are implicitly using Is = as in Case Is = 7.)

Comma-Separated Expressions

A comparison expression can include a series of expressions separated by commas. If the test value matches any of the comparison values, the program executes the corresponding code.

For example, the following code examines the department_name variable. If department_name is "R & D," "Test," or "Computer Operations," the code adds the text "Building 10" to the address_text string. If department_name is "Finance," "Purchasing," or "Accounting," the code adds "Building 7" to the address. More Case clauses could check for other department_name values and the code could include an Else statement.

Select Case department_name
    Case "R & D", "Test", "Computer Operations"
        address_text &= "Building 10"
    Case "Finance", "Purchasing", "Accounting"
        address_text &= "Building 7"
    ...
End Select

Note that you cannot use comma-separated expressions in a Case Else clause. For example, the following code doesn't work:

Case Else, "Corporate" ' This doesn't work.

You can mix and match constants, To, and Is expressions in a single Case clause, as shown in the following example. This code checks the variable item_code and calls subroutine DoSomething if the value is less than 10, between 30 and 40 inclusive, exactly equal to 100, or greater than 200.

Select Case item_code
    Case Is < 10, 30 To 40, 100, Is > 200
        DoSomething()
    ...
End Select

Complex comparison expressions are sometimes difficult to read. If an expression is too complicated, you should consider rewriting the code to make it easier to understand. Storing values in temporary variables can help. The following code shows a version of the preceding code that's a bit easier to understand:

Select Case item_code
    Case Is < 10
        DoSomething()
    Case 30 To 40
        DoSomething()
    Case 100
        DoSomething()
    Case Is > 200
        DoSomething()
    ...
End Select

While this version is easier to understand, it is more verbose and uses duplicated code (repeated calls to DoSomething). The following version uses a temporary variable to make the code easy to read without the duplication.

' See if we must do something.
Dim must_do_something As Boolean = False
If item_code < 10 Then must_do_something = True
If (item_code >= 30) AndAlso (item_code <= 40) Then must_do_something = True
If item_code = 100 Then must_do_something = True
If item_code > 200 Then must_do_something = True

If must_do_something Then
    ...
End If

Enumerated Values

Select Case statements work very naturally with lists of discrete values. You can have a separate Case statement for each value, or you can list multiple values for one Case statement in a comma-separated list.

Enumerated types defined by the Enum statement also work with discrete values, so they work well with Select Case statements. The enumerated type defines the values and the Select Case statement uses them, as shown in the following code fragment:

Private Enum JobStates
    Pending
    Assigned
    InProgress
    ReadyToTest
    Tested
    Released
End Enum
Private m_JobState As JobStates
...
Select Case m_JobState
    Case Pending
        ...
    Case Assigned
        ...
    Case InProgress
        ...
    Case ReadyToTest
        ...
    Case Tested
        ...
    Case Released
        ...
End Select

To catch bugs when changing an enumerated type, many developers include a Case Else statement that throws an exception. If you later add a new value to the enumerated type but forget to add corresponding code to the Select Case statement, the Select Case statement throws an error when it sees the new value, so you can fix the code.

For more information on enumerated types, see the section "Enumerated Data Types" in Chapter 15, "Data Types, Variables, and Constants."

IIf

The IIf statement evaluates a Boolean expression and then returns one of two values, depending on whether the expression is true or false. This statement may look more like an assignment statement or a function call than a decision statement such as If Then.

The syntax is as follows:

variable = IIf(condition, value_if_true, value_if_false)

For example, the following code examines an Employee object's IsManager property. If IsManager is True, the code sets the employee's Salary to 90,000. If IsManager is False, the code sets the employee's Salary to 10,000.

emp.Salary = IIf(emp.IsManager, 90000, 10000)

Note that the IIf statement returns an Object data type. If you have Option Strict turned on, Visual Basic will not allow this statement, because it assigns a result of type Object to an Integer variable. To satisfy Visual Basic, you must explicitly convert the value into an Integer, as in the following code:

emp.Salary = CInt(IIf(emp.IsManager, 90000, 10000))

The IIf statement has several drawbacks. First, it is confusing. When you type an IIf statement, IntelliSense will remind you that its parameters give a condition, a True value, and a False value. When you are reading the code, however, you must remember what the different parts of the statement mean. If you use IIf in some other statement, the chances for confusion increase. For example, consider the following code:

For i = 1 To CType(IIf(employees_loaded, num_employees, 0), Integer)
    ' Process employee i.
    ...
Next i

Code is generally much easier to understand if you replace IIf with an appropriate If Then statement.

Another drawback to IIf is that it evaluates both the True and False values whether the condition is true or false. For example, consider the following code. If the Boolean use_groups is True, the code sets num_objects to the result of the CountGroups function. If use_groups is False, the code sets num_objects to the result of the CountIndividuals function. IIf evaluates both functions no matter which value it actually needs. If the functions are time-consuming or executed inside a large loop, using IIf can waste a lot of time.

num_objects = CType(
    IIf(use_groups,
        CountGroups(),
        CountIndividuals()),
    Integer)

For an even more dangerous example, consider the following code. If data_loaded is True, this statement sets num_loaded = num_employees. If data_loaded is False, the code sets num_loaded to the value returned by the LoadEmployees function (which loads the employees and returns the number of employees it loaded).

num_loaded = CType(IIf(data_loaded, num_employees, LoadEmployees()), Integer)

IIf evaluates both the value num_employees and the value LoadEmployees() no matter what. If the employees are already loaded, IIf calls LoadEmployees() to load the employees again, ignores the returned result, and sets num_loaded = num_employees. LoadEmployees may waste quite a lot of time loading the data that is already loaded. Even worse, the program may not be able to handle loading the data when it is already loaded.

A final drawback to IIf is that it is slower than a comparable If Then Else statement. In one test, IIf took roughly twice as long as a comparable If Then statement.

One case where you can argue that IIf is easier to understand is when you have a long series of very simple statements. In that case, IIf statements may allow you to easily see the common features in the code and notice if anything looks wrong. For example, the following code initializes several text boxes using strings. It uses an IIf statement to set a text box's value to <Missing> if the string is not yet initialized.

txtLastName.Text = IIf(last_name Is Nothing, "<Missing>", last_name)
txtFirstName.Text = IIf(first_name Is Nothing, "<Missing>", first_name)
txtStreet.Text = IIf(street Is Nothing, "<Missing>", street)
txtCity.Text = IIf(city Is Nothing, "<Missing>", city)
txtState.Text = IIf(state Is Nothing, "<Missing>", state)
txtZip.Text = IIf(zip Is Nothing, "<Missing>", zip)

To avoid confusing side effects, use IIf only if it makes the code easier to understand.

If

The If statement resolves some of the problems with the IIf statement. It evaluates a Boolean expression and then returns one of two values, depending on whether the expression is true or false, as IIf does. The difference is that If only evaluates the return value that it actually returns.

For example, the following code examines an Employee object's IsManager property. If IsManager is True, the code sets the employee's Salary to the result returned by the GetManagerSalary function and never calls function GetEmployeeSalary. If IsManager is False, the code sets the employee's Salary to the result of the GetEmployeeSalary function and never calls function GetManagerSalary. This is different from the way IIf works because IIf would call both functions no matter which value it was going to return. If the functions are time-consuming, using If can make the code more efficient.

emp.Salary = If(emp.IsManager, GetManagerSalary(), GetEmployeeSalary())

Other than the fact that If doesn't evaluate both of its possible return values, it behaves just as IIf does. For more information, see the previous section.

Choose

The IIf statement uses a Boolean expression to pick between two values. The Choose statement uses an integer to decide among any number of options. The syntax is as follows:

variable = Choose(index, value1, value2, value3, value4, ... )

If the index parameter is 1, Choose returns the first value, value1; if index is 2, Choose returns value2; and so forth. If index is less than 1 or greater than the number of values in the parameter list, Choose returns Nothing.

This statement has the same drawbacks as IIf. Choose evaluates all of the result values no matter which one is selected, so it can slow performance. It can be particularly confusing if the values are functions with side effects.

Often Choose is more confusing than a comparable Select Case statement. If the values look dissimilar (mixing integers, objects, function calls, and so forth), involve complicated functions, or are wrapped across multiple lines, a Select Case statement may be easier to read.

However, if the Choose statement's values are short and easy to understand, and the statement contains many values, the Choose statement may be easier to read. For example, the following Choose and Select Case statements do the same thing. Because the Choose statement's values are short and easy to understand, this statement is easy to read. The Select Case statement is rather long. If the program had more choices, the Select Case statement would be even longer, making it more difficult to read.

fruit = Choose(index, "apple", "banana", "cherry", "date")

Select Case index
    Case 1
        fruit = "apple"
    Case 2
        fruit = "banana"
    Case 3
        fruit = "cherry"
    Case 4
        fruit = "date"
End Select

Although it's not always clear whether a Choose statement or a Select Case statement will be easier to read, Select Case is certainly faster. In one test, Choose took more than five times as long as Select Case. If the code lies inside a frequently executed loop, the speed difference may be an issue.

Choose and Select Case are not your only options. You can also store the program's choices in an array, and then use the index to pick an item from the array. For example, the following code stores the strings from the previous example in the values array. It then uses the index to pick the right choice from the array.

Dim fruit_names() As String = {"apple", "banana", "cherry", "date"}

fruit = fruit_names(index - 1)

This version makes you think about the code in a different way. It requires that you know that the fruit_names array contains the names of the fruits that the program needs. If you understand the array's purpose, then the assignment statement is easy to understand.

The assignment code is even slightly faster than Select Case, at least if you can initialize the fruit_names array ahead of time.

If you find Choose easy to understand and it doesn't make your code more difficult to read in your particular circumstances, by all means use it. If Select Case seems clearer, use that. If you will need to perform the assignment many times and pre-building an array of values makes sense, using a value array might improve your performance.

LOOPING STATEMENTS

Looping statements make the program execute a series of statements repeatedly. The loop can run for a fixed number of repetitions, run while some condition is true, or run while some condition is false.

Broadly speaking, there are two types of looping statement. For loops execute a certain number of times that (in theory at least) is known. For example, a For loop may execute a series of statements exactly 10 times. Or, it may execute the statements once for each object in a certain collection. If you know how many items are in the collection, you know the number of times the loop will execute.

A While loop executes while a condition is true or until a condition is met. Without a lot more information about the application, it is impossible to tell how many times the code will execute. For example, suppose a program uses the InputBox function to get names from the user until the user clicks the Cancel button. In that case, there's no way for the program to guess how many values the user will enter before canceling.

The following sections describe the looping statements supported by Visual Basic .NET. The next two sections describe For loops, and the sections after those describe While loops.

For Next

The For Next loop is the most common type of looping statement in Visual Basic. The syntax is as follows:

For variable [As data_type] = start_value To stop_value [Step increment]
    statements
    [Exit For]
    statements
    [Continue For]
    statements
Next [variable]

The value variable is the looping variable that controls the loop. When the program reaches the For statement, it sets variable equal to start_value. It then compares variable to stop_value. If variable has passed stop_value, the loop exits. Note that the loop may not execute even once depending on the start and stop values.

For example, the following loop runs for the values employee_num = 1, employee_num = 2, . . ., employee_num = num_employees. If the program has not loaded any employees so num_employees = 0, the code inside the loop is not executed at all.

For employee_num = 1 To num_employees
    ProcessEmployee(employee_num)
Next employee_num

After it compares variable to stop_value, the program executes the statements inside the loop. It then adds increment to variable and starts the process over, again comparing variable to stop_value. If you omit increment, the program uses an increment of 1.

Note that increment can be negative or a fractional number, as in the following example:

For i As Integer = 3 To 1 Step −0.5
    Debug.WriteLine(i)
Next i

If increment is positive, the program executes as long as variable <= stop_value. If increment is negative, the program executes as long as variable >= stop_value. This means that the loop would not execute infinitely if increment were to move variable away from stop_value. For example, in the following code start_value = 1 and increment = −1. The variable i would take the values i = 1, i = 0, i = −1, and so forth, so i will never reach the stop_value of 2. However, because increment is negative, the loop only executes while i >= 2. Because i starts with the value 1, the program immediately exits and the loop doesn't execute at all.

For i As Integer = 1 To 2 Step −1
    Debug.WriteLine(i)
Next i

Visual Basic doesn't require that you include the variable's name in the Next statement, although this makes the code easier to read. If you do specify the name in the Next statement, it must match the name you use in the For statement.

If you do not specify the looping variable's data type in the For statement and Option Explicit is on and Option Infer is off, then you must declare the variable before the loop. For example, the following loop declares the variable i outside of the loop:

Dim i As Integer

For i = 1 To 10
    Debug.WriteLine(i)
Next i

Declaring the looping variable in the For statement is a good practice. It limits the scope of the variable so you don't need to remember what the variable is for in other pieces of code. It keeps the variable's declaration close to the code where it is used, so it's easier to remember the variable's data type. It also lets you more easily reuse counter variables without fear of confusion. If you have several loops that need an arbitrarily named looping variable, they can all declare and use the variable i without interfering with each other.

The program calculates its start_value and stop_value before the loop begins and it never recalculates them, even if their values change. For example, the following code loops from 1 to this_customer.Orders(1).NumItems. The program calculates this_customer.Orders(1).NumItems before executing the loop and doesn't recalculate that value even if it later changes. This saves the program time, particularly for long expressions such as this one, which could take a noticeable amount of time to reevaluate each time through a long loop.

For item_num As Integer = 1 To this_customer.Orders(1).NumItems
    this_customer.ProcessItem(item_num)
Next item_num

If you must reevaluate stop_value every time the loop executes, use a While loop instead of a For loop.

The Exit For statement allows the program to leave a For loop before it would normally finish. For example, the following code loops through the employees array. When it finds an entry with the IsManager property set to True, it saves the employee's index and uses Exit For to immediately stop looping.

Dim manager_index As Integer

For i As Integer = employees.GetLowerBound(0) To employees.GetUpperBound(0)
    If employees(i).IsManager Then
        manager_index = i
        Exit For
    End If
Next i

The Exit For statement exits only the For loop immediately surrounding the statement. If a For loop is nested within another For loop, the Exit For statement only exits the inner loop.

The Continue For statement makes the loop jump back to its For statement, increment its looping variable, and start the loop over again. This is particularly useful if the program doesn't need to execute the rest of the steps within the loop's body and wants to start the next iteration quickly.

Non-Integer For Next Loops

Usually a For Next loop's control variable is an integral data type such as an Integer or Long, but it can be any of the fundamental Visual Basic numeric data types. For example, the following code uses a variable declared as Single to display the values 1.0, 1.5, 2.0, 2.5, and 3.0:

For x As Single = 1 To 3 Step 0.5
    Debug.WriteLine(x.ToString("0.0"))
Next x

Because floating-point numbers cannot exactly represent every possible value, these data types are subject to rounding errors that can lead to unexpected results in For Next loops. The preceding code works as you would expect, at least on my computer. The following code, however, has problems. Ideally, this code would display values between 1 and 2, incrementing them by 1/7. Because of rounding errors, however, the value of x after seven trips through the loop is approximately 1.85714316. The program adds 1/7 to this and gets 2.0000003065381731. This is greater than the stopping value 2, so the program exits the loop and the Debug statement does not execute for x = 2.

For x As Single = 1 To 2 Step 1 / 7
    Debug.WriteLine(x)
Next x

One solution to this type of problem is to convert the code into a loop that uses an Integer control variable. Integer variables do not have the same problems with rounding errors that floating-point numbers do, so you have more precise control over the values used in the loop.

The following code does roughly the same thing as the previous code. It uses an Integer control variable, however, so this loop executes exactly eight times as desired. The final value printed into the Output window by the program is 2.

Dim x As Single

x = 1
For i As Integer = 1 To 8
    Debug.WriteLine(x)
    x += CSng(1 / 7)
Next i

If you look at the value of variable x in the debugger, you will find that its real value during the last trip through the loop is roughly 2.0000001702989851. If this variable were controlling the For loop, the program would see that this value is greater than 2, so it would not display its final value.

For Each

A For Each loop iterates over the items in a collection, array, or other container class that supports For Each loops. The syntax is as follows:

For Each variable [As object_type] In group
    statements
    [Exit For]
    statements
    [Continue For]
    statements
Next [variable]

Here, group is a collection, array, or other object that supports For Each. As in For Next loops, the control variable must be declared either in or before the For statement if you have Option Explicit on and Option Infer off.

The control variable must be of a data type compatible with the objects contained in the group. If the group contains Employee objects, the variable could be an Employee object. It could also be a generic Object or any other class that readily converts into an Employee object. For example, if Employee inherits from the Person class, then the variable could be of type Person.

Visual Basic doesn't automatically understand what kinds of objects are stored in a collection or array until it tries to use them. If the control variable's type is not compatible with an object's type, the program generates an error when the For Each loop tries to assign the control variable to that object's value.

That means if a collection or array contains more than one type of object, the control variable must be of a type that can hold all of the objects. If the objects in a collection do not inherit from a common ancestor class, the code must use a control variable of type Object.

Like For Next loops, For Each loops support the Exit For and Continue For statements.

As is the case with For Next loops, declaring the looping variable in the For Each statement is a good practice. It limits the scope of the variable, so you don't need to remember what the variable is for in other pieces of code. It keeps the variable's declaration close to the code where it is used, so it's easier to remember the variable's data type. It also lets you more easily reuse counter variables without fear of confusion. If you have several loops that need an arbitrarily named looping variable, they can all declare and use the variable obj, person, or whatever else makes sense without interfering with each other.

Your code can change the value of the control variable inside the loop, but that has no effect on the loop's progress through the collection or array. The loop resets the variable to the next object inside the group and continues as if you had never changed the variable's value. To avoid confusion, don't bother.

Changes to a collection are immediately reflected in the loop. For example, if the statements inside the loop add a new object to the end of the collection, then the loop continues until it processes the new item. Similarly, if the loop's code removes an item from the end of the collection (that it has not yet reached), the loop does not process that item.

The exact effect on the loop depends on whether the item added or removed comes before or after the object the loop is currently processing. For example, if you remove an item before the current item, the loop has already examined that item, so there is no change to the loop's behavior. If you remove an item after the current one, the loop doesn't examine it. If you remove the current item, the loop seems to get confused and exits without raising an error.

Additions and deletions to an array are not reflected in the loop. If you use a ReDim statement to add items to the end of the array, the loop does not process them. If you try to access those objects, however, the program generates an "Index was outside the bounds of the array" error.

If you use ReDim to remove items from the end of the array, the loop processes those items anyway! If you modify the values in the array, for example, you change an object's properties or set an array entry to an entirely new object, the loop sees the changes.

To avoid all these possible sources of confusion, don't modify a collection or array while a For Each loop is examining its contents.

One common scenario when dealing with collections is to examine every item in the collection and remove some of them. If you use a For Each loop, removing the loop's current item makes the loop exit prematurely.

Another approach that seems like it might work (but doesn't) is to use a For Next loop, as shown in the following code. If the code removes an object from the collection, the loop skips the next item because its index has been reduced by one and the loop has already passed that position in the collection. Worse still, the control variable i will increase until it reaches the original value of employees.Count. If the loop has removed any objects, the collection no longer holds that many items. The code tries to access an index beyond the end of the collection and throws an error.

Dim emp As Employee

For i As Integer = 1 To employees.Count
    emp = CType(employees(i), Employee)
    If emp.IsManager Then employees.Remove(i)
Next i

One solution to this problem is to use a For Next loop to examine the collection's objects in reverse order, as shown in the following example. In this version, the code never needs to use an index after it has been deleted because it is counting backward. The index of an object in the collection also doesn't change unless that object has already been examined by the loop. The loop examines every item exactly once, no matter which objects are removed.

For i As Integer = employees.Count To 1 Step −1
    emp = CType(employees(i), Employee)
    If emp.IsManager Then employees.Remove(i)
Next i

Enumerators

An enumerator is an object that lets you move through the objects contained by some sort of container class. For example, collections, arrays, and hash tables provide enumerators. This section discusses enumerators for collections, but the same ideas apply for these other classes.

You can use an enumerator to view the objects in a collection but not to modify the collection itself. You can use the enumerator to alter the objects in the collection, but you can generally not use it to add, remove, or rearrange the objects in the collection.

Initially, an enumerator is positioned before the first item in the collection. Your code can use the enumerator's MoveNext method to step to the next object in the collection. MoveNext returns True if it successfully moves to a new object or False if there are no more objects in the collection.

The Reset method restores the enumerator to its original position before the first object, so you can step through the collection again.

The Current method returns the object that the enumerator is currently reading. Note that Current returns a generic Object, so you will probably need to use CType to convert the result into a more specific data type before you use it. Invoking Current throws an error if the enumerator is not currently reading any object. That happens if the enumerator is before the first object or after the last object.

The following example uses an enumerator to loop through the items in a collection named m_Employees. It declares an Employee variable named emp and an IEnumerator object named employee_enumerator. It uses the collection's GetEnumerator method to obtain an enumerator for the collection. The program then enters a While loop. If employee_enumerator.MoveNext returns True, the enumerator has successfully moved to the next object in the collection. As long as it has read an object, the program uses CType to convert the generic object returned by Current into an Employee object, and it displays the Employee object's Title, FirstName, and LastName values. When it has finished processing all of the objects in the collection, employee_enumerator.MoveNext returns False and the While loop ends.

Dim emp As Employee
Dim employee_enumerator As IEnumerator
employee_enumerator = m_Employees.GetEnumerator()
Do While (employee_enumerator.MoveNext)
    emp = CType(employee_enumerator.Current, Employee)
    Debug.WriteLine(emp.Title & " " &.FirstName & " " & emp.LastName)
Loop

A For Each loop provides roughly the same access to the items in a container class as an enumerator. Under some circumstances, however, an enumerator may provide a more natural way to loop through a container class than a For Each loop. For example, an enumerator can skip several items without examining them closely. You can also use an enumerator's Reset method to restart the enumeration. To restart a For Each loop, you would need to repeat the loop, possibly by placing it inside yet another loop that determines when to stop looping.

The Visual Basic documentation states that an enumerator is valid only as long as you do not modify the collection. If you add or remove an object to or from the collection, the enumerator throws an "invalid operation" exception the next time you use it. In at least some cases, however, this doesn't seem to be true, and an enumerator can still work even if you modify its collection. This could lead to extremely confusing situations, however. To avoid unnecessary confusion, do not modify a collection while you are accessing it with an enumerator. (If you really must modify the collection, try the techniques described in the "Creative Collections" tip earlier in this chapter.)

The IEnumerable interface defines the features needed for enumerators so any class that implements the IEnumerable interface provides enumerators. Any class that supports For Each must also implement the IEnumerable interface, so any class that supports For Each also supports enumerators. A few of the classes that implement IEnumerable include the following:

Array

HybridDictionary

SqlDataReader

ArrayList

ListDictionary

Stack

Collection

MessageQueue

String

CollectionBase

OdbcDataReader

StringCollection

ControlCollection

OleDbDataReader

StringDictionary

DataView

OracleDataReader

TableCellCollection

DictionaryBase

Queue

TableRowCollection

DictionaryEntries

ReadOnlyCollectionBase

XmlNode

Hashtable

SortedList

XmlNodeList

Iterators

An iterator is similar in concept to an enumerator. It also provides methods that allow you to step through the objects in some sort of container object. Iterators are more specialized than enumerators and work with a particular kind of class. Although you can use a nonspecific IEnumerator object to step through the items contained in any class that implements IEnumerable (an array, collection, hash table, or whatever), a certain iterator class is associated with a specific container class.

For example, a GraphicsPath object represents a series of connected lines and curves. A GraphicsPathIterator object can step through the line and curve data contained in a GraphicsPath object.

Iterators are much more specialized than enumerators. How you use them depends on what you need to do and on the kind of iterator, so they are not described in detail here.

Do Loop Statements

Visual Basic .NET supports three basic forms of Do Loop statements. The first form is a loop that repeats forever. The syntax is as follows:

Do
    statements
    [Exit Do]
    statements
    [Continue Do]
    statements
Loop

This kind of Do Loop executes the code it contains until the program somehow ends the loop. The following loop processes work orders. It calls the WorkOrderAvailable function to see if a work order is available. If an order is available, the code calls ProcessWorkOrder to process it. The code then repeats the loop to look for another work order.

Do
    ' See if a work order is available.
    If WorkOrderAvailable() Then
          ' Process the next work order.
        ProcessWorkOrder()
    End If
Loop

This example keeps checking for work orders forever. Most programs include some method for the loop to end so that the program can eventually stop. For example, the loop might use the Exit Do statement described shortly to end the loop if the user clicks a Stop button.

The second and third forms of Do Loop statements both include a test to determine whether they should continue looping. The difference between the two versions is where they place the test.

The next version of Do Loop places its test at the beginning, so the test is evaluated before the code is executed. If the test initially indicates that the loop should not continue, the statements inside the loop are never executed. The syntax is as follows:

Do {While | Until} condition
    statements
    [Exit Do]
    statements
    [Continue Do]
    statements
Loop

The final version of Do Loop places its test at the end. In this version, the statements inside the loop are executed before the loop performs its test. That means that the code is always executed at least once. The syntax is as follows:

Do
    statements
    [Exit Do]
    statements
    [Continue Do]
    statements
Loop {While | Until} condition

If the code uses the While keyword, the loop executes as long as the condition is true. If the code uses the Until keyword, the loop executes as long as the condition is false. Note that the statement Until condition is equivalent to While Not condition. Visual Basic provides these two variations so that you can pick the one that makes your code more readable. Use the one that makes the most sense to you.

The Exit Do statement allows the program to leave the nearest enclosing loop before it would normally finish. The Continue Do statement makes the loop jump back to its Do statement and start the loop over again. This is particularly useful if the program doesn't need to execute the rest of the steps within the loop and wants to quickly start the next iteration. Unlike a For loop, the Do loop does not automatically increment a looping variable or move to the next object in a collection. The code must explicitly change the loop's condition before calling Continue Do or else the loop will continue forever.

While End

A While End loop is equivalent to a Do While loop. The syntax is as follows:

While condition
    statements
    [Exit While]
    statements
    [Continue While]
    statements
End While

This is equivalent to the following Do While loop:

Do While condition
    statements
    [Exit Do]
    statements
    [Continue Do]
    statements
Loop

The Exit While statement exits a While End loop just as an Exit Do statement exits a Do While Loop. Similarly, Continue While makes the program return to the top of the loop just as Continue Do does for Do loops.

The difference between While End and Do While Loop is stylistic, and you can use whichever seems clearer to you. Because Do Loop provides more flexibility, having four different versions using While or Until at the start or finish of the loop, you might want to stick to them for consistency's sake.

Exit and Continue

The Exit and Continue statements are described in the previous sections, but they deserve a quick summary.

The Exit statement lets you end a loop early. The Continue statement lets you jump to the start of a loop before reaching its end.

Both of these statements work only on the innermost loop of the appropriate type. For example, an Exit For statement exits the innermost For loop surrounding the statement.

Example program ExitAndContinue, which is available for download on the book's web site, demonstrates the Exit and Continue statements.

GOTO

A GoTo statement unconditionally tells the program to jump to a specific location in the code. Because it tells the program what to do, it is a program control statement. The syntax is as follows:

GoTo line_label
    ...
line_label:
    ...

Though GoTo by itself isn't a decision statement, it is often used to mimic a decision statement. For example, the following code fragment uses GoTo to mimic an If Then Else statement. It examines the purchase_total variable. If purchase_total is less than 1000, the code jumps to the line labeled SmallOrder. If purchase_total is greater than or equal to 1000, the program continues to execute the code that processes a larger order.

If purchase_total < 1000 Then GoTo SmallOrder
    ' Process a large order.
    ...
    Exit Sub

SmallOrder:
    ' Process a small order.
    ...

The following code does roughly the same thing as the preceding version but without the GoTo statement:

If purchase_total < 1000 Then
    ' Process a large order.
    ...
Else
    ' Process a small order.
    ...
End If

Similarly, GoTo is sometimes used to build a loop. The following code uses GoTo to jump backward in the code to call subroutine DoSomething 10 times:

Dim i As Integer = 1
StartLoop:
    DoSomething()
    i += 1
    If i <= 10 Then GoTo StartLoop

The following code does the same thing without the GoTo statement:

For i As Integer = 1 To 10
    DoSomething()
Next i

The problem with the GoTo statement is its flexibility. By using GoTo in a haphazard way, an undisciplined programmer can make the program jump all over the place with little rhyme or reason. This can lead to spaghetti code (so called because a diagram showing the program's flow of control can look like a pile of spaghetti) that is extremely difficult to understand, debug, and maintain.

Many programming teams prohibit any use of GoTo because it can lead to this kind of code. Some even believe GoTo should be removed from the Visual Basic language. You can always use If Then Else statements, For Next loops, While loops, and other control statements in place of GoTo statements, so GoTo is not absolutely necessary.

However, some programmers feel that GoTo simplifies code under certain very specific circumstances. The following code begins by performing some sort of initialization. It may open databases, create temporary files, connect to the Internet, and perform other startup chores. It then executes a series of tasks, each of which may fail or otherwise make it pointless for the program to continue. If any of these steps sets the variable should_stop to True, the program uses a GoTo statement to jump to its clean-up code. This code closes any open database, deletes temporary files, closes permanent files, and performs any other necessary clean up chores.

' Get started, open database, open files, etc.
    Initialize()

    ' Perform a long series of tasks.
    DoStuff1()
    If should_stop Then GoTo CleanUp

    DoStuff2()
    If should_stop Then GoTo CleanUp

    DoStuff3()
    If should_stop Then GoTo CleanUp

    ...

CleanUp:
    ' Close database, delete temporary files, etc.
    PerformCleanUp()

The GoTo statement in this code lets the program jump to the clean-up code any time it needs to stop performing its tasks. That may be because a task failed, the user canceled the operation, or all the tasks are finished.

Note that this is a very specific use of GoTo. The code only jumps forward, never backward. It also only jumps to clean-up code, not to some arbitrary point in the code. These facts help make the GoTo statement easier to understand and prevent spaghetti code.

The following code does the same thing as the preceding version without using GoTo. At each step, the program checks the value of should_stop to see if it should continue working through its tasks.

' Get started, open database, open files, etc.
Initialize()

' Perform a long series of tasks.
DoStuff1()

If Not should_stop Then DoStuff2()

If Not should_stop Then DoStuff3()

' Close database, delete temporary files, etc.
PerformCleanUp()

The following code shows another version that doesn't use GoTo. This version places the code that formerly contained the GoTo statement in a new subroutine. Instead of using GoTo, this routine uses Exit Sub to stop performing tasks early if necessary.

Sub DoWork() '
    ' Get started, open database, open files, etc.
    Initialize()

    ' Perform all of the tasks.
    PerformTasks()

    ' Close database, delete temporary files, etc.
    PerformCleanUp()
End Sub

' Perform a long series of tasks.
Sub PerformTasks()
    DoStuff1()
    If should_stop Then Exit Sub

    DoStuff2()
    If should_stop Then Exit Sub

    DoStuff3()
    If should_stop Then Exit Sub
End Sub

Conceptually, an Exit Sub statement is little different from a GoTo statement. After all, it, too, is an unconditional jump command. However, Exit Sub has a very specific, well-known effect: it makes the program stop executing the current subroutine. It cannot make the program jump around arbitrarily, possibly leading to spaghetti code.

If you ever feel tempted to use GoTo, take a few moments to think about ways you might rewrite the code. If the only ways you can think of to rewrite the code are more confusing than the original version, go ahead and use GoTo. You should probably add some fairly detailed comments to ensure that the GoTo statement doesn't cause trouble later.

SUMMARY

Control statements form the heart of any program. Decision statements determine what commands are executed, and looping statements determine how many times they are executed.

Single-line and multiline If Then statements, as well as Select Case, are the most commonly used decision statements. IIf and Choose statements are often more confusing and sometimes slower, so usually you should use If Then and Select Case statements instead. Under some specific circumstances, however, IIf and Choose may make your code more readable. Use your judgment and pick the method that makes the most sense in your application.

For Next, For Each, and Do Loop are the most common looping statements. Some container classes also support enumerators that let you step through the items in the container. An enumerator can be more natural than a For Each loop under some circumstances.

A While End loop is equivalent to Do While loop. You can use whichever you think makes more sense, although you might want to use Do While because it is more consistent with the other forms of Do Loop.

Finally, the GoTo statement is often used in a decision statement or to create a loop. Unfortunately, undisciplined use of GoTo statements can lead to spaghetti code that is extremely hard to understand, debug, and maintain. To avoid later frustration, you should avoid using GoTo statements whenever possible, and provide good comments where GoTo is necessary. Some programmers use GoTo in very specialized cases, whereas others avoid it at all costs. You can always rewrite code to avoid GoTo statements, and usually that is better in the long run.

Using the control statements described in this chapter, you can build extremely complex and powerful applications. In fact, you can build applications that are so complex that it is difficult to ensure that they work correctly. Even a relatively simple application sometimes encounters errors. Chapter 19, "Error Handling," explains how you can protect an application from these and other unexpected errors and let it take action to correct any problems or at least to avoid crashing.

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

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