Chapter 3. Visual Basic Programming Essentials

The one thing you should have learned about programming in Visual Basic so far is that an application is made up of small, self-contained segments. The code you write isn't a monolithic listing; it's made up of small segments called procedures, and you work on one procedure at a time.

In this chapter we'll explore the two types of procedures supported by Visual Basic: subroutines and functions — the building blocks of your applications. We'll discuss them in detail: how to call them with arguments and how to retrieve the results returned by the functions. You'll learn how to use the built-in functions that come with the language as well as how to write your own subroutines and functions.

The statements that make up the core of the language are actually very few. The flexibility of any programming language is based on its capacity to alter the sequence in which the statements are executed through a set of so-called flow-control statements. These are the statements that literally make decisions and react differently depending on the data, user actions, or external conditions. Among other topics, in this chapter you'll learn how to do the following:

  • Use Visual Basic's flow-control statements

  • Write subroutines and functions

  • Pass arguments to subroutines and functions

Flow-Control Statements

What makes programming languages so flexible and capable of handling every situation and programming challenge with a relatively small set of commands is their capability to examine external or internal conditions and act accordingly. Programs aren't monolithic sets of commands that carry out the same calculations every time they are executed; this is what calculators (and extremely simple programs) do. Instead, they adjust their behavior depending on the data supplied; on external conditions, such as a mouse click or the existence of a peripheral; even on a coding mistake you haven't caught during your tests.

In effect, the statements discussed in the first half of this chapter are what programming is all about. Without the capability to control the flow of the program, computers would just be bulky calculators. You have seen how to use the If statement to alter the flow of execution in previous chapters, and I assume you're somewhat familiar with these kinds of statements. In this section, you'll find a formal discussion of flow-control statements, which are grouped into two major categories: decision statements and looping statements.

Decision Statements

Applications need a mechanism to test conditions, and they take a different course of action depending on the outcome of the test. Visual Basic provides three statements that allow you to alter the course of the application based on the outcome of a condition:

  • If...Then

  • If...Then...Else

  • Select Case

If...Then Statements

The If...Then statement tests an expression, which is known as a condition. If the condition is True, the program executes the statement(s) that follow the Then keyword up to the End If statement, which terminates the conditional statement. The If...Then statement can have a single-line or a multiple-line syntax. To execute one statement conditionally, use the single-line syntax as follows:

If condition Then statement

To execute multiple statements conditionally, embed the statements within an If and End If statement, as follows:

If condition Then
   ' Statement
   ' Statement
End If

Conditions are logical expressions that evaluate to a True/False value and they usually contain comparison operators — equals (=), different (<>), less than (<), greater than (>), less than or equal to (<=), and so on — and logical operators — And, Or, Xor, and Not. Here are a few examples of valid conditions:

If (age1 < age2) And (age1 > 12) Then ...
If score1 = score2 Then ...

The parentheses are not really needed in the first sample expression, but they make the code a little easier to read and understand. Sometimes parentheses are mandatory, to specify the order in which the expression's parts will be evaluated, just as math formulae may require parentheses to indicate the precedence of calculations.

The expressions can get quite complicated. The following expression evaluates to True if the date1 variable represents a date earlier than the year 2005 and either one of the score1 and score2 variables exceeds 90 (you could use it locate high scores in a specific year):

If (date1 < #1/1/2005) And (score1 > 90 Or score2 > 90) Then
     ' statements
End If

The parentheses around the last part of the comparison are mandatory because we want the compiler to perform the following comparison first:

score1 > 90 Or score2 > 90

If either variable exceeds 90, the preceding expression evaluates to True and the initial condition is reduced to the following:

If (date1 < #1/1/2008) And (True) Then

The compiler will evaluate the first part of the expression (it will compare two dates) and finally it will combine two Boolean values with the And operator: If both values are True, the entire condition is True; otherwise, it's False. If you didn't use parentheses, the compiler would evaluate the three parts of the expression:

expression1: date1 < #1/1/2008#
expression2: score1 < 90
expression3: score2 < 90

Then it would combine expression1 with expression2 using the And operator, and finally it would combine the result with expression3 using the Or operator. If score2 were greater than 90, the entire expression would evaluate to True, regardless of the value of the date1 and score1 variables.

If...Then...Else Statements

A variation of the If...Then statement is the If...Then...Else statement, which executes one block of statements if the condition is True and another block of statements if the condition is False. The syntax of the If...Then...Else statement is as follows:

If condition Then
   statementblock1
Else
   statementblock2
End If

Visual Basic evaluates the condition; if it's True, VB executes the first block of statements and then jumps to the statement following the End If statement. If the condition is False, Visual Basic ignores the first block of statements and executes the block following the Else keyword.

A third variation of the If...Then...Else statement uses several conditions, with the ElseIf keyword:

If condition1 Then
   statementblock1
ElseIf condition2 Then
   statementblock2
ElseIf condition3 Then
   statementblock3
Else
statementblock4
End If

You can have any number of ElseIf clauses. The conditions are evaluated from the top, and if one of them is True, the corresponding block of statements is executed. The Else clause, which is optional, will be executed if none of the previous expressions is True. Listing 3.1 is an example of an If statement with ElseIf clauses.

Example 3.1. Multiple ElseIf statements

score = InputBox("Enter score")
If score < 50 Then
   Result = "Failed"
ElseIf score < 75 Then
   Result = "Pass"
ElseIf score < 90 Then
   Result = "Very Good"
Else
   Result = "Excellent"
End If
MsgBox Result

The order of the comparisons is vital when you're using multiple ElseIf statements. Had you written the previous code segment with the first two conditions switched, like the following segment, the results would be quite unexpected:

If score < 75 Then
   Result = "Pass"
ElseIf score < 50 Then
   Result = "Failed"
ElseIf score < 90 Then
   Result = "Very Good"
Else
   Result = "Excellent"
End If

Let's assume that score is 49. The code would compare the score variable to the value 75. Because 49 is less than 75, it would assign the value Pass to the variable Result, and then it would skip the remaining clauses. Thus, a student who scored 49 would have passed the test! So be extremely careful and test your code thoroughly if it uses multiple ElseIf clauses. You must either make sure they're listed in the proper order or use upper and lower limits, as in the sidebar "Multiple If...Then Structures versus ElseIf." It goes without saying that such a code segment should be tested for all possible intervals of the score variable.

Select Case Statements

An alternative to the efficient but difficult-to-read code of the multiple ElseIf structure is the Select Case structure, which compares the same expression to different values. The advantage of the Select Case statement over multiple If...Then...ElseIf statements is that it makes the code easier to read and maintain.

The Select Case structure evaluates a single expression at the top of the structure. The result of the expression is then compared with several values; if it matches one of them, the corresponding block of statements is executed. Here's the syntax of the Select Case statement:

Select Case expression
   Case value1
     ' statementblock1
   Case value2
     ' statementblock2
      .
   Case Else
      statementblockN
End Select

A practical example based on the Select Case statement is shown in Listing 3.2.

Example 3.2. Using the Select Case statement

Dim Message As String
Select Case Now.DayOfWeek
    Case DayOfWeek.Monday
        message = "Have a nice week"
    Case DayOfWeek.Friday
        message = "Have a nice weekend"
    Case Else
        message = "Welcome back! "
End Select
MsgBox(message)

In the listing, the expression that's evaluated at the beginning of the statement is the Now.DayOfWeek method. This method returns a member of the DayOfWeek enumeration, and you can use the names of these members in your code to make it easier to read. The value of this expression is compared with the values that follow each Case keyword. If they match, the block of statements up to the next Case keyword is executed, and the program skips to the statement following the End Select statement. The block of the Case Else statement is optional and is executed if none of the previous cases matches the expression. The first two Case statements take care of Fridays and Mondays, and the Case Else statement takes care of the other days.

Some Case statements can be followed by multiple values, which are separated by commas. Listing 3.3 is a revised version of the previous example. The code of Listing 3.3 handles Saturdays and Sundays.

Example 3.3. A Select Case statement with multiple cases per clause

Select Case Now.DayOfWeek
    Case DayOfWeek.Monday
        message = "Have a nice week"
    Case DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday
        message = "Welcome back!"
    Case DayOfWeek.Friday, DayOfWeek.Saturday, DayOfWeek.Sunday
        message = "Have a nice weekend!"
End Select
MsgBox(message)

Monday, weekends, and weekdays are handled separately by three Case statements. The second Case statement handles multiple values (all workdays except for Monday and Friday). Monday is handled by a separate Case statement. This structure doesn't contain a Case Else statement because all possible values are examined in the Case statements; the DayOfWeek method can't return another value.

The Case statements can get a little more complex. For example, you may want to distinguish a case where the variable is larger (or smaller) than a value. To implement this logic, use the Is keyword, as in the following code segment that distinguishes between the first and second half of the month:

Select Now.Day
    Case Is < 15
        MsgBox("It's the first half of the month")
    Case Is >= 15
        MsgBox("It's the second half of the month")
End Select

Short-Circuiting Expression Evaluation

A common pitfall of evaluating expressions with VB is to attempt to compare a Nothing value to something. An object variable that hasn't been set to a value can't be used in calculations or comparisons. Consider the following statements:

Dim B As SolidBrush
B = New SolidBrush(Color.Cyan)
If B.Color = Color.White Then
    MsgBox("Please select another brush color")
End If

These statements create a SolidBrush object variable, the B variable, and then examine the brush color and prohibit the user from drawing with a white brush. The second statement initializes the brush to the cyan color. (Every shape drawn with this brush will appear in cyan.) If you instead attempted to use the B variable without initializing it (that is, if you had not included the line that creates a new SolidBrush object), a runtime exception would be thrown: the infamous NullReferenceException would be thrown when the program gets to the If statement because the B variable has no value (it's Nothing), and the code attempts to compare it to something. Nothing values can't be compared to anything. Comment out the second statement by inserting a single quote in front of it and then execute the code to see what will happen. Then restore the statement by removing the comment mark.

Actually, as soon as you comment out the statement that initializes the B variable, the editor will underline the B variable and it will generate the warning Variable B is used before it has been assigned a value. A null reference exception could result at runtime.

Let's fix it by making sure that B is not Nothing:

If B IsNot Nothing And B.Color = Color.White Then
    MsgBox("Please select another brush color")
End If

The If statement should compare the Color property of the B object, only if the B object is not Nothing. But this isn't the case. The AND operator evaluates all terms in the expression and then combines their results (True or False values) to determine the value of the expression. If they're all True, the result is also True. However, it won't skip the evaluation of some terms as soon as it hits a False value. To avoid unnecessary comparisons, use the AndAlso operator. The AndAlso operator does what the And operator should have done in the first place: it evaluates the expressions from left to right, and when it encounters a False value, it stops evaluating the remaining terms because they won't affect the result. If one of its operands is False, the entire expression will evaluate to False. In other words, if B is Nothing, there's no reason to examine its color; the entire expression will evaluate to False, regardless of the brush color. Here's how to use the AndAlso operator:

If B IsNot Nothing AndAlso B.Color = Color.White Then
    MsgBox("Please select another brush color")
End If

The AndAlso operator is said to short-circuit the evaluation of the entire expression as soon as it runs into a False value. As soon as one of the parts in an AndAlso operation turns out to be False, the entire expression is False and there's no need to evaluate the remaining terms.

There's an equivalent operator for short-circuiting OR expressions: the OrElse operator. The OrElse operator can speed the evaluation of logical expressions a little by returning True when the first operand evaluates to True (the result of the OR operation will be True, regardless of the value of the second operand). Another good reason for short-circuiting expression evaluation is to help performance. If the second term of an And expression takes longer to execute (it has to access a remote database, for example), you can use the AndAlso operator to make sure that it's not executed when it's not needed.

Loop Statements

Loop statements allow you to execute one or more lines of code repetitively. Many tasks consist of operations that must be repeated over and over again, and loop statements are an important part of any programming language. Visual Basic supports the following loop statements:

  • For...Next

  • Do...Loop

  • While...End While

For...Next Loops

Unlike the other two loops, the For...Next loop requires that you know the number of times that the statements in the loop will be executed. The For...Next loop has the following syntax:

For counter = start To end [Step increment]
  ' statements
Next [counter]

The keywords in the square brackets are optional. The arguments counter, start, end, and increment are all numeric. The loop is executed as many times as required for the counter variable's value to reach (or exceed) the end value. The variable that appears next to the For keyword is the loop's counter, or control variable.

In executing a For...Next loop, Visual Basic does the following:

  1. Sets the counter variable equal to the start variable (this is the control variable's initial value).

  2. Tests to see whether counter is greater than end. If so, it exits the loop without executing the statements in the loop's body, not even once. If increment is negative, Visual Basic tests to see whether the counter value is less than the end value. If it is, it exits the loop.

  3. Executes the statements in the block.

  4. Increases the counter variable by the amount specified with the increment argument following the Step keyword. If the increment argument isn't specified, counter is increased by 1. If Step is a negative value, counter is decreased accordingly.

  5. Continues with step 2.

The For...Next loop in Listing 3.4 scans all the elements of the numeric array data and calculates their average.

Example 3.4. Iterating an array with a For...Next loop

Dim i As Integer, total As Double
For i = 0 To data.Length
   total = total + data(i)
Next i
Debug.WriteLine (total / Data.Length)

The single most important thing to keep in mind when working with For...Next loops is that the loop's ending value is set at the beginning of the loop. Changing the value of the end variable in the loop's body won't have any effect. For example, the following loop will be executed 10 times, not 100 times:

Dim endValue As Integer = 10
Dim i as Integer
For i = 0 To endValue
   endValue = 100
   ' more statements
Next i

You can, however, adjust the value of the counter variable from within the loop. The following is an example of an endless (or infinite) loop:

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

This loop never ends because the loop's control variable, in effect, is never increased. (If you try this, press Ctrl+Break to interrupt the endless loop.)

For Each...Next Loops

This is a variation of the classic For loop and it's used to iterate through the items of a collection or array. Let's say you have declared an array of strings like the following:

Dim months() As String = _
           {"January", "February", "March", "April", "May", "June"}

You can iterate through the month names with a For Each loop like the one that follows:

For Each month As String In months
    Debug.WriteLine(month)
Next

The month control variable need not be declared if the Infer option is on. The compiler will figure out the type of the control variable based on the types of the values you're iterating over, which in our example are strings. You can easily write the equivalent For...Next loop for the same task, but the For Each loop is more elegant. It also provides a variable that represents the current item at each iteration.

Let's look at a more interesting example of the For Each loop to get an idea of the type of operations it's best suited for. The Process class of the Framework provides methods for inspecting the process running on the target computer at any time. These are the processes you see in the Processes tab of the Task Manager. Each process is represented by a Process object, which in turn exposes several useful properties (such as the name of the process, the physical memory it's using, and so on) as well as methods to manipulate the processes, including the Kill method that terminates a process.

The GetProcesses method returns an array of Process objects, one for each running process. To iterate through the current processes, you can use a For Each loop like the following:

Dim processes() = Process.GetProcesses
For Each Proc As Process In processes
    Debug.WriteLine(Proc.ProcessName & "   " &
                         Proc.PrivateMemorySize64.ToString)
Next

This loop will display a list like the following in the Output window:

taskeng            10588160
svchost            11476992
YahooMessenger     20496384
sqlservr           104538112
svchost            4255744
svchost            6549504
SearchIndexer      53612544
sqlwriter          3715072
searchFilterHost   3514368
cmd                2080768
iexplore           250073088

As you can see, the For Each loop is much more elegant than a For...Next loop when it comes to iterating through the items of a collection. The loop's counter is not an index, but an object that represents the current entity — provided that all elements are of the same type, of course. Many developers use a For Each...Next loop whenever possible, even in situations where a trivial For...Next loop would suffice. Compare the loops in Listing 3.5 and Listing 3.6 for iterating through the elements of an array of integers.

Example 3.5. Using a For...Next loop

Dim numbers() = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
For i As Integer = 1 to numbers.Length - 1
    '    Process value numbers(i)
Next

Example 3.6. Using a For Each...Next loop

Dim numbers() = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
For Each number As Integer In numbers
    '    Process value number
Next

Although I declare the control variable in both of the preceding loops, this isn't mandatory as long as you have turned on type inference. The compiler will figure out the proper type from the type of the objects that make up the collection you're iterating.

Do Loops

The Do...Loop statement executes a block of statements for as long as a condition is True or until a condition becomes True. Visual Basic evaluates an expression (the loop's condition), and if it's True, the statements in the loop body are executed. The expression is evaluated either at the beginning of the loop (before any statements are executed) or at the end of the loop (after the block statements are executed at least once). If the expression is False, the program's execution continues with the statement following the loop. These two variations use the keywords While and Until to specify how long the statements will be executed. To execute a block of statements while a condition is True, use the following syntax:

Do While condition
  ' statement-block
Loop

To execute a block of statements until the condition becomes True, use the following syntax:

Do Until condition
  ' statement-block
Loop

When Visual Basic executes these loops, it first evaluates condition. If condition is False, a Do...While loop is skipped (the statements aren't even executed once) but a Do...Until loop is executed. When the Loop statement is reached, Visual Basic evaluates the expression again; it repeats the statement block of the Do...While loop if the expression is True or repeats the statements of the Do...Until loop if the expression is False. In short, the Do...While loop is executed when the condition is True (while the condition is True), and the Do...Until loop is executed when the condition is False (until the condition becomes True).

A last variation of the Do statement, the Do...Loop statement, allows you to always evaluate the condition at the end of the loop, even in a While loop. Here's the syntax of both types of loop, with the evaluation of the condition at the end of the loop:

Do
  ' statement-block
Loop While condition

Do
  ' statement-block
Loop Until condition

As you can guess, the statements in the loop's body are executed at least once, even in the case of the While loop, because no testing takes place as the loop is entered.

Here's a typical example of using a Do...Loop: Suppose that the variable MyText holds some text (like the Text property of a TextBox control) and you want to count the words in the text. (We'll assume that there are no multiple spaces in the text and that the space character separates successive words.) To locate an instance of a character in a string, use the IndexOf method of the String class. This method accepts two arguments: the starting location of the search and the character being searched. The following loop repeats for as long as there are spaces in the text. Each time the IndexOf method finds another space in the text, it returns the location of the space. When there are no more spaces in the text, the IndexOf method returns the value −1, which signals the end of the loop, as shown:

Dim MyText As String =
        "The quick brown fox jumped over the lazy dogs"
Dim position, words As Integer
position = 0
words = 0
Do While position >= 0
    position = MyText.IndexOf(" ", position + 1)
    words += 1
Loop
MsgBox("There are " & words & " words in the text")

The Do...Loop is executed while the IndexOf method function returns a positive number, which means that there are more spaces (and therefore words) in the text. The variable position holds the location of each successive space character in the text. The search for the next space starts at the location of the current space plus 1 (so the program won't keep finding the same space). For each space found, the program increases the value of the words variable, which holds the total number of words when the loop ends. By the way, there are simpler methods of breaking a string into its constituent words, such as the Split method of the String class. This is just an example of the Do...While loop.

You might notice a problem with the previous code segment: It assumes that the text contains at least one word. You should insert an If statement that detects zero-length strings and doesn't attempt to count words in them. You can also use the IsNullOrEmpty method of the String class, which returns True if a String variable is empty or Nothing.

You can code the same routine with the Until keyword. In this case, you must continue searching for spaces until position becomes −1. Here's the same code with a different loop:

Dim position As Integer = 0
Dim words As Integer = 0
Do Until position = −1
   position = MyText.IndexOf(" ", position + 1)
   words = words + 1
Loop
MsgBox("There are " & words & " words in the text")

While Loops

The While...End While loop executes a block of statements as long as a condition is True. The loop has the following syntax:

While condition
  ' statement-block
End While

If condition is True, the statements in the block are executed. When the End While statement is reached, control is returned to the While statement, which evaluates condition again. If condition is still True, the process is repeated. If condition is False, the program resumes with the statement following End While.

The loop in Listing 3.7 prompts the user for numeric data. The user can type a negative value to indicate he's done entering values and terminate the loop. As long as the user enters positive numeric values, the program keeps adding them to the total variable.

Example 3.7. Reading an unknown number of values

Dim number, total As Double
number = 0
While number => 0
   total = total + number
   number = InputBox("Please enter another value")
End While

I've assigned the value 0 to the number variable before the loop starts because this value isn't negative and doesn't affect the total.

Sometimes, the condition that determines when the loop will terminate can't be evaluated at the top of the loop. In these cases, we declare a Boolean value and set it to True or False from within the loop's body. Here's the outline of such a loop:

Dim repeatLoop As Boolean
repeatLoop = True
While repeatLoop
   ' statements
   If condition Then
      repeatLoop = True
   Else
      repeatLoop = False
   End If
End While

You may also see an odd loop statement like the following one:

While True
   ' statements
End While

It's also common to express the True condition in one of the following two forms:

While 1 = 1

or

While True

Now, there's no good reason to use statements like these; I guess they're leftovers from old programs. The seemingly endless loops must be terminated from within the body using an Exit While statement, which is called when a condition becomes True or False. The following loop terminates when a condition is met in the loop's body:

While True
   ' statements
   If condition Then Exit While
   ' more statements
End While

Of course, this code isn't elegant and you should avoid it, except when you're implementing some complicated logic that can't be easily coded differently.

Nested Control Structures

You can place, or nest, control structures inside other control structures (such as an If...Then block within a For...Next loop) or nest multiple If...Then blocks within one another. Control structures in Visual Basic can be nested in as many levels as you want. The editor automatically indents the bodies of nested decision and loop structures to make the program easier to read.

When you nest control structures, you must make sure that they open and close within the same structure. In other words, you can't start a For...Next loop in an If statement and close the loop after the corresponding End If. The following code segment demonstrates how to nest several flow-control statements:

For a = 1 To 100
   ' statements
   If a = 99 Then
      ' statements
   End If
   While b < a
      ' statements
      If total <= 0 Then
         ' statements
      End If
   End While
   For c = 1 to a
      ' statements
   Next c
Next a

I show the names of the control variables after the Next statements to make the code more readable to humans. To find the matching closing statement (Next, End If, or End While), move down from the opening statement until you hit a line that starts at the same column. This is the matching closing statement. Notice that you don't have to align the nested structures yourself; the editor reformats the code automatically as you type. It also inserts the matching closing statement — the End If statement is inserted automatically as soon as you enter an If statement and press Enter, for example. Not only that, but as soon as you click in a control or loop statement, the editor highlights the corresponding ending statement.

Listing 3.8 shows a typical situation with nested loops. The two nested loops scan all the elements of a two-dimensional array.

Example 3.8. Iterating through a two-dimensional array

Dim Array2D(6, 4) As Integer
Dim iRow, iCol As Integer
For iRow = 0 To Array2D.GetUpperBound(0)
    For iCol = 0 To Array2D.GetUpperBound(1)
        Array2D(iRow, iCol) = iRow * 100 + iCol
        Debug.Write(iRow & ", " & iCol & " = " &
                    Array2D(iRow, iCol) & " ")
    Next iCol
    Debug.WriteLine()
Next iRow

The outer loop (with the iRow counter) scans each row of the array. At each iteration, the inner loop scans all the elements in the row specified by the counter of the outer loop (iRow). After the inner loop completes, the counter of the outer loop is increased by one, and the inner loop is executed again — this time to scan the elements of the next row. The loop's body consists of two statements that assign a value to the current array element and then print it in the Output window. The current element at each iteration is Array2D(iRow, iCol).

Another typical example of nested loops is the code that iterates through the cells of a ListView control. (This control is discussed in Chapter 7, "More Windows Controls," and also in the tutorial "The ListView and TreeView controls.") The ListView control is basically a grid — not an editable one, I'm afraid, but an excellent tool for displaying tabular data. To iterate through the control's cells, you must set up a loop that iterates through its rows and a nested loop that iterates through the current row's cells. Each row of the ListView control is a ListViewItem object, which provides information about the rows' cells through the SubItems property. The SubItems property is an array of values, one for each cell of the grid's row. The expression ListView1.Items(2).SubItems(1).Text returns the contents of the second cell in the control's third row. The following code segment iterates through the cells of any ListView control, regardless of the number of rows and columns it contains:

For iRow As Integer = 0 To ListView1.Items.Count – 1
   Dim LI As ListViewItem = ListView1.Items(iRow)
   For iCol As Integer = 0 To LI.SubItems.Count – 1
     ' process cell LI.SubItems(iCol)
   Next
Next

The two nested For...Next loops are quite old-fashioned. In modern VB, you'd write the same code as follows:

Dim str As String = ""
For Each LI As ListViewItem In ListView1.Items
    For Each cell In LI.SubItems
        str = str & cell.Text.ToString & vbTab
    Next
    str = str & vbCrLf
Next
MsgBox(str)

The preceding code segment gradually builds a string with the contents of the ListView control, separating cells in the same row with a tab (vbTab constant) and consecutive rows with a line feed (vbCrLf constant). You can also nest multiple If statements. The code in Listing 3.9 tests a user-supplied value to determine whether it's positive; if so, it determines whether the value exceeds a certain limit.

Example 3.9. Simple nested If statements

Dim Income As Decimal
Income = Convert.ToDecimal(InputBox("Enter your income"))
If Income > 0 Then
If Income > 12000 Then
      MsgBox "You will pay taxes this year"
   Else
      MsgBox "You won't pay any taxes this year"
   End If
Else
   MsgBox "Bummer"
End If

The Income variable is first compared with zero. If it's negative, the Else clause of the If...Then statement is executed. If it's positive, it's compared with the value 12,000, and depending on the outcome, a different message is displayed. The code segment shown here doesn't perform any extensive validations and assumes that the user won't enter a string when prompted for income.

The Exit and Continue Statements

The Exit statement allows you to prematurely exit from a block of statements in a control structure, from a loop, or even from a procedure. Suppose that you have a For...Next loop that calculates the square root of a series of numbers. Because the square root of negative numbers can't be calculated (the Math.Sqrt method will generate a runtime error), you might want to halt the operation if the array contains an invalid value. To exit the loop prematurely, use the Exit For statement as follows:

For i = 0 To UBound(nArray)
    If nArray(i) < 0 Then
        MsgBox("Can't complete calculations" & vbCrLf &
               "Item " & i.ToString & " is negative! "
        Exit For
    End If
    nArray(i) = Math.Sqrt(nArray(i))
Next

If a negative element is found in this loop, the program exits the loop and continues with the statement following the Next statement.

There are similar Exit statements for the Do loop (Exit Do), the While loop (Exit While), the Select statement (Exit Select), and functions and subroutines (Exit Function and Exit Sub). If the previous loop was part of a function, you might want to display an error and exit not only the loop, but also the function itself by using the Exit Function statement.

Sometimes you may need to continue with the following iteration instead of exiting the loop (in other words, skip the body of the loop and continue with the following value). In these cases, you can use the Continue statement (Continue For for For... Next loops, Continue While for While loops, and so on).

Writing and Using Procedures

Now that you have seen the decision and looping structures of Visual Basic, let's move on to procedures. In traditional programming languages, procedures are the basic building blocks of every application. And what exactly is a traditional language? Well, a procedural language, of course. A procedural language is one that requires you to specify how to carry out specific tasks by writing procedures. A procedure is a series of statements that tell the computer how to carry out a specific task. The task could be the calculation of a loan's monthly payment (a task that can be coded literally with a single statement) or the retrieval of weather data from a remote server. In any case, the body of statements form a unit of code that can be invoked by name, not unlike scripts or macro commands but much more flexible and certainly more complex.

The idea of breaking a large application into smaller, more manageable sections is not new to computing. Few tasks, programming or otherwise, can be managed as a whole. Using event handlers is just one example of breaking a large application into smaller tasks.

For example, when you write code for a control's Click event, you concentrate on the event at hand — namely, how the program should react to the Click event. What happens when the control is double-clicked or when another control is clicked is something you will worry about later — in another control's event handler. This divide-and-conquer approach isn't unique to programming events. It permeates the Visual Basic language, and developers write even the longest applications by breaking them into small, well-defined, easily managed tasks. Each task is performed by a procedure that is written and tested separately from the others. As mentioned earlier, the two types of procedures supported by Visual Basic are subroutines and functions.

Subroutines perform actions and they don't return any result. Functions, on the other hand, perform some calculations and return a value. This is the only difference between subroutines and functions. Both subroutines and functions can accept arguments (values you pass to the procedure when you call it). Usually, the arguments are the values on which the procedure's code acts. Arguments and the related keywords are discussed in detail later in this chapter.

Subroutines

A subroutine is a block of statements that carries out a well-defined task. The block of statements is placed within a set of Sub...End Sub statements and can be invoked by name. The following subroutine displays the current date in a message box:

Sub ShowDate()
   MsgBox("Today's date is " & Now().ToShortDateString)
End Sub

To use it in your code, you can just enter the name of the function in a line of its own:

ShowDate()

To experiment with the procedures presented in this chapter, start a new Windows project, place a button on the main form, and then enter the definition of the ShowDate() subroutine outside any event handler. In the button's Click event handler, enter the statement ShowDate(). If you run the application and click the button, the current date will appear on a message box. The single statement in the event handler calls the ShowDate() subroutine, which displays the current date. Your main program calls the subroutine by name and it doesn't care how complex the subroutine is.

Normally, the task performed by a subroutine is more sophisticated than this, but even this simple subroutine is a block of code isolated from the rest of the application. The statements in a subroutine are executed, and when the End Sub statement is reached, control returns to the calling code. It's possible to exit a subroutine prematurely by using the Exit Sub statement. In effect, a subroutine is a set of statements that perform a very specific task, and you can invoke them by name. Use subroutines to break your code into smaller, more manageable units and certainly if you're coding tasks that may be used in multiple parts of the application. Note that the ShowDate() subroutine can be called from any event handler in the current form.

All variables declared within a subroutine are local to that subroutine. When the subroutine exits, all variables declared in it cease to exist.

Most procedures also accept and act upon arguments. The ShowDate() subroutine displays the current date in a message box. If you want to display any other date, you have to implement it differently and add an argument to the subroutine:

Sub ShowDate(ByVal aDate As Date)
   MsgBox(aDate.ToShortDateString)
End Sub

aDate is a variable that holds the date to be displayed; its type is Date. The ByVal keyword means that the subroutine sees a copy of the variable, not the variable itself. What this means practically is that the subroutine can't change the value of the variable passed by the calling code.

To display a specific date with the second implementation of the subroutine, use a statement like the following:

Dim myBirthDate = #2/9/1960#
ShowDate(myBirthDate)

Or, you can pass the value to be displayed directly without the use of an intermediate variable:

ShowDate(#2/9/1960#)

If you later decide to change the format of the date, there's only one place in your code you must edit — the statement that displays the date from within the ShowDate() subroutine.

Functions

A function is similar to a subroutine, but a function returns a result. Because they return values, functions — like variables — have types. The value you pass back to the calling program from a function is called the return value, and its type determines the type of the function. Functions accept arguments, just like subroutines. The statements that make up a function are placed in a set of Function...End Function statements, as shown here:

Function NextDay() As Date
    Dim theNextDay As Date
    theNextDay = Now.AddDays(1)
    Return theNextDay
End Function

Functions are called like subroutines — by name — but their return value is usually assigned to a variable. To call the NextDay() function, use a statement like this:

Dim tomorrow As Date = NextDay()

Because functions have types like variables, they can be used anywhere you'd use a variable name. You will find several examples of practical functions later in this chapter, both built-in functions that are part of the language and custom functions. Subroutines are being gradually replaced by functions, and in some languages there are no subroutines, just functions. Even if you need a procedure to perform some task without returning a value, you can implement it as a function that returns a True/False value to indicate whether the operations completed successfully or not.

The Function keyword is followed by the function name and the As keyword that specifies its type, similar to a variable declaration. Inside the preceding sample function, AddDays is a method of the Date type, and it adds a number of days to a date value. The NextDay() function returns tomorrow's date by adding one day to the current date. NextDay() is a custom function, which calls the built-in AddDays method to complete its calculations.

The result of a function is returned to the calling program with the Return statement, which is followed by the value you want to return from your function. This value, which is usually a variable, must be of the same type as the function. In our example, the Return statement happens to be the last statement in the function, but it could appear anywhere; it could even appear several times in the function's code. The first time a Return statement is executed, the function terminates and control is returned to the calling code.

You can also return a value to the calling routine by assigning the result to the name of the function. The following is an alternate method of coding the NextDay() function:

Function NextDay() As Date
   NextDay = Now.AddDays(1)
End Function

Notice that this time I've assigned the result of the calculation to the function's name directly and haven't use a Return statement. This assignment, however, doesn't terminate the function as the Return statement does. It sets up the function's return value, but the function will terminate when the End Function statement is reached or when an Exit Function statement is encountered.

Similar to the naming of variables, a custom function has a name that must be unique in its scope (which is also true for subroutines, of course). If you declare a function in a form, the function name must be unique in the form. If you declare a function as Public or Friend, its name must be unique in the project. Functions have the same scope rules as variables and can be prefixed by many of the same keywords. In effect, you can modify the default scope of a function with the keywords Public, Private, Protected, Friend, and Protected Friend. In addition, functions have types, just like variables, and they're declared with the As keyword.

Suppose that the function CountWords() counts the number of words and the function CountChars() counts the number of characters in a string. The average length of a word in the string longString could be calculated as follows:

Dim longString As String, avgLen As Double
longString = TextBox1.Text
avgLen = CountChars(longString) / CountWords(longString)

The first executable statement gets the text of a TextBox control and assigns it to a variable, which is then used as an argument to the two functions. When the third statement executes, Visual Basic first calls the functions CountChars() and CountWords() with the specified arguments and then divides the results they return.

The CountWords() function uses the Split method, which isolates the words in a string and returns them as an array of strings. Then the code reads the length of the array, which equals the number of words in the string. The Split method accepts as an argument a character, which is the delimiter it will use to break the string into words. The space character being passed as an argument is enclosed in double quotes, but this is a string, not a character. It's a string that contains a single character, but a string nevertheless. To convert the space string (" ") into a character value, you just append the c character to the string. The number of words in the string is the length of the array that holds the individual words, the words array.

Function CountWords(ByVal longString As String) As Integer
    Dim words = longString.Split(" "c)
    Return words.Length
End Function

Function CountChars(ByVal longString As String) As Integer
    longString = longString.Replace(" ", "")
    Return longString.Length
End Function

You can call functions in the same way that you call subroutines, but the result won't be stored anywhere. For example, the function Convert() might convert the text in a text box to uppercase and return the number of characters it converted. Normally, you'd call this function as follows:

nChars = Convert()

If you don't care about the return value — you only want to update the text on a TextBox control — you would call the Convert() function with the following statement:

Convert()

Most of the procedures in an application are functions, not subroutines. The reason is that a function can return (at the very least) a True/False value that indicates whether it completed successfully or not. In the remainder of this chapter, I will focus on functions, but the same principles apply to subroutines as well, except for the return value.

Arguments

Subroutines and functions aren't entirely isolated from the rest of the application. Most procedures accept arguments from the calling program. Recall that an argument is a value you pass to the procedure and on which the procedure usually acts. This is how subroutines and functions communicate with the rest of the application.

Subroutines and functions may accept any number of arguments, and you must supply a value for each argument of the procedure when you call it. Some of the arguments may be optional, which means you can omit them; you will see shortly how to handle optional arguments.

Let's implement a simple custom function to demonstrate the use of arguments. The Min() function, shown next, is a custom function that accepts two arguments and returns the smaller one. Once you write the function, you can call it from within your code just like any built-in function. The difference is that while the built-in functions are always available, the custom functions are available only to the project in which they are declared. Here's the implementation of the Min() function:

Function Min(ByVal a As Single, ByVal b As Single) As Single
   Min = IIf(a < b, a, b)
End Function

Interestingly, the Min() function calls the IIf() built-in function. IIf() is a built-in function that evaluates the first argument, which is a logical expression. If the expression is True, the IIf() function returns the second argument. If the expression is False, the function returns the third argument.

To call the Min() custom function, use a few statements like the following:

Dim val1 As Single = 33.001
Dim val2 As Single = 33.0011
Dim smallerVal as Single
smallerVal = Min(val1, val2)
MsgBox("The smaller value is " & smallerVal)

If you execute these statements (place them in a button's Click event handler), you will see the following in a message box:

The smaller value is 33.001

Or you can insert these statements in the Main subroutine of a Console application and replace the call to the MsgBox function with a call to the Console.WriteLine method to see the output on a console window. Here's what the entire Console application's code should look like:

Module Module1

    Sub Main()
        Dim val1 As Single = 33.001
        Dim val2 As Single = 33.0011
        Dim smallerVal As Single
        smallerVal = Min(val1, val2)
        Console.WriteLine("The smaller value is " & smallerVal)
        Console.ReadKey()
    End Sub

    Function Min(ByVal a As Single, ByVal b As Single) As Single
        Min = IIf(a < b, a, b)
    End Function

End Module

If you attempt to call the same function with two Double values with a statement like the following, you will see the value 3.33 in the Immediate window:

Debug.WriteLine(Min(3.33000000111, 3.33000000222))

The compiler converted the two values from Double to Single data type and returned one of them. Which one is it? It doesn't make a difference because when converted to Single, both values are the same.

Interesting things will happen if you attempt to use the Min() function with the Strict option turned on. Insert the statement Option Strict On at the very beginning of the file, or set Option Strict to On in the Compile tab of the project's properties pages. The editor will underline the statement that implements the Min() function: the IIf() function. The IIf() function accepts two Object variables as arguments and returns one of them as its result. The Strict option prevents the compiler from converting an Object to a numeric variable. To use the IIf() function with the Strict option, you must change the Min implementation as follows:

Function Min(ByVal a As Object, ByVal b As Object) As Object
   Min = IIf(Val(a) < Val(b), a, b)
End Function

It's possible to implement a Min() function that can compare arguments of all types (integers, strings, dates, and so on).

Argument-Passing Mechanisms

One of the most important topics in implementing your own procedures is the mechanism used to pass arguments. The examples so far have used the default mechanism: passing arguments by value. The other mechanism is passing them by reference. Although most programmers use the default mechanism, it's important to know the difference between the two mechanisms and when to use each.

By Value versus by Reference

When you pass an argument by value, the procedure sees only a copy of the argument. Even if the procedure changes this copy, the changes aren't reflected in the original variable passed to the procedure. The benefit of passing arguments by value is that the argument values are isolated from the procedure and only the code segment in which they are declared can change their values.

In VB 6, the default argument-passing mechanism was by reference, and this is something you should be aware of, especially if you're migrating VB 6 code to VB 2010.

To specify the arguments that will be passed by value, use the ByVal keyword in front of the argument's name. If you omit the ByVal keyword, the editor will insert it automatically because it's the default option. Suppose you're creating a function called Degrees() to convert temperatures from degrees Celsius to degrees Fahrenheit. To declare that the Degrees() function's argument is passed by value, use the ByVal keyword in the argument's declaration as follows:

Function Degrees(ByVal Celsius as Single) As Single
   Return((9 / 5) * Celsius + 32)
End Function

To see what the ByVal keyword does, add a line that changes the value of the argument in the function:

Function Degrees(ByVal Celsius as Single) As Single
   Dim Fahrenheit = (9 / 5) * Celsius + 32
   Celsius = 0
   Return Fahrenheit
End Function

Now call the function as follows:

Dim CTemp As Single = InputBox("Enter temperature in degrees Celsius")
Dim FTemp As Single = Degrees(CTemp)
MsgBox(CTemp.ToString & " degrees Celsius are " &
       FTemp & " degrees Fahrenheit")

If you enter the value 32, the following message is displayed:

32 degrees Celsius are 89.6 degrees Fahrenheit

The value you specify in the InputBox is stored in the CTemp variable, which is then passed to the Degrees() function. The function's return value is then stored in the FTemp variable, which is then used to display the result to the user. Replace the ByVal keyword with the ByRef keyword in the function's definition and call the function with the same statements; the program will display the following message:

0 degrees Celsius are 89.6 degrees Fahrenheit

When the CTemp argument was passed to the Degrees() function, its value was 32. But the function changed its value, and upon return it was 0. Because the argument was passed by reference, any changes made by the procedure affected the calling code's variable that was passed into the function.

Returning Multiple Values

If you want to write a function that returns more than a single result, you will most likely pass additional arguments by reference and set their values from within the function's code. The CalculateStatistics() function, shown a little later in this section, calculates the basic statistics of a data set. The values of the data set are stored in an array, which is passed to the function by reference. The CalculateStatistics() function must return two values: the average and standard deviation of the data set. Here's the declaration of the CalculateStatistics() function:

Function CalculateStatistics(ByRef Data() As Double,
            ByRef Avg As Double, ByRef StDev As Double) As Integer

The declaration of a procedure is basically its signature; it includes all the information you need in order to use the procedure in your call. Of course, you have to know what the various arguments represent, but this is where the documentation comes in. It's also possible to add a short description for each argument, which will appear in the IntelliSense box, as with the built-in procedures. You'll learn how to automate the documentation of your procedures in the last section of this chapter. The function returns an integer, which is the number of values in the data set. The two important values calculated by the function are returned in the Avg and StDev arguments:

Function CalculateStatistics(ByRef Data() As Double,
              ByRef Avg As Double, ByRef StDev As Double) As Integer
   Dim i As Integer, sum As Double, sumSqr As Double, points As Integer
   points = Data.Length
   For i = 0 To points - 1
      sum = sum + Data(i)
      sumSqr = sumSqr + Data(i) ˆ 2
   Next
   Avg = sum / points
   StDev = System.Math.Sqrt(sumSqr / points – Avg ˆ 2)
   Return(points)
End Function

To call the CalculateStatistics() function from within your code, set up an array of Doubles and declare two variables that will hold the average and standard deviation of the data set:

Dim Values() = {102.301, 391.200, 19.29, 179.42, 88.031, 208.01}
Dim average, deviation As Double
Dim points As Integer
points = CalculateStatistics(Values, average, deviation)
Debug.WriteLine(points & " values processed.")
Debug.WriteLine("The average is " & average.ToString & " and ")
Debug.WriteLine("the standard deviation is " & deviation.ToString)

The simplest method for a function to effectively return multiple values is to pass to it arguments by reference using the ByRef keyword. However, the definition of your functions might become cluttered, especially if you want to return more than a few values. Another problem with this technique is that it's not clear whether an argument must be set before calling the function. As you will see shortly, it is possible for a function to return an array or a custom structure with fields for any number of values.

Built-in Functions

VB provides many functions that implement common or complicated tasks, and you can look them up in the documentation. (You'll find them in the Visual Studio

Built-in Functions

The Pmt() function, for example, calculates the monthly payments on a loan. All you have to know is the arguments you must pass to the function and how to retrieve the result. The syntax of the Pmt() function is as follows, where MPay is the monthly payment, Rate is the monthly interest rate, and NPer is the number of payments (the duration of the loan in months). PV is the loan's present value (the amount you took from the bank):

MPay = Pmt(Rate, NPer, PV, FV, Due)

Due is an optional argument that specifies when the payments are due (the beginning or the end of the month), and FV is another optional argument that specifies the future value of an amount. This isn't needed in the case of a loan, but it can help you calculate how much money you should deposit each month to accumulate a target amount over a given time. (The amount returned by the Pmt() function is negative because it's a negative cash flow — it's money you owe — so pay attention to the sign of your values.)

To calculate the monthly payment for a $20,000 loan paid off over a period of six years at a fixed interest rate of 7.25%, you call the Pmt() function, as shown in Listing 3.10.

Example 3.10. Using the Pmt() built-in function

Dim mPay, totalPay As Double
Dim Duration As Integer = 6 * 12
Dim Rate As Single = (7.25 / 100) / 12
Dim Amount As Single = 20000
mPay = -Pmt(Rate, Duration, Amount)
totalPay = mPay * Duration
MsgBox("Your monthly payment will be " & mPay.ToString("C") &
           vbCrLf & "You will pay back a total of " &
           totalPay.ToString("C"))

Notice that the interest (7.25%) is divided by 12 because the function requires the monthly interest. The value returned by the function is the monthly payment for the loan specified with the Duration, Amount, and Rate variables. If you place the preceding lines in the Click event handler of a button, run the project, and then click the button, the following message will appear in a message box:

Your monthly payment will be $343.39
You will pay back a total of $24,723.80

Let's say you want to accumulate $40,000 over the next 15 years by making monthly deposits of equal amounts. To calculate the monthly deposit amount, you must call the Pmt() function, passing 0 as the present value and the target amount as the future value. Replace the statements in the button's Click event handler with the following and run the project:

Dim mPay As Double
Dim Duration As Integer = 15 * 12
Dim Rate As Single = (4.0 / 100.0) / 12
Dim Amount As Single = −40000.0
mPay = Pmt(Rate, Duration, 0, Amount)
MsgBox("A monthly deposit of " & mPay.ToString("C") & vbCrLf &
       "every month will yield $40,000 in 15 years")

It turns out that if you want to accumulate $40,000 over the next 15 years to send your kid to college, assuming a constant interest rate of 4%, you must deposit $162.54 every month. You'll put out almost $30,000, and the rest will be the interest you earn.

Pmt() is one of the simpler financial functions provided by the Framework, but most of us would find it really difficult to write the code for this function. Because financial calculations are quite common in business programming, many of the functions you might need already exist, and all you need to know is how to call them. If you're developing financial applications, you should look up the financial functions in the documentation. You can experiment with the Pmt() function (and learn the basics of banking) by finding out the monthly payments for a loan and an investment of the same amount and same duration, using the current interest rates.

Let's look at another useful built-in function, the MonthName() function, which accepts as an argument a month number and returns the name of the month. This function is not as trivial as you might think because it returns the month name or its abbreviation in the language of the current culture. The MonthName() function accepts as arguments the month number and a True/False value that determines whether it will return the abbreviation or the full name of the month. The following statements display the name of the current month (both the abbreviation and the full name). Every time you execute these statements, you will see the current month's name in the current language:

Dim mName As String
mName = MonthName(Now.Month, True)
MsgBox(mName)   ' prints "Jan"
mName = MonthName(Now.Month, False)
MsgBox(mName)   ' prints "January"

A similar function, the WeekDayName() function, returns the name of the week for a specific weekday. This function accepts an additional argument that determines the first day of the week. (See the documentation for more information on the syntax of the WeekDayName() function.)

The primary role of functions is to extend the functionality of the language. Many functions that perform rather common practical operations have been included in the language, but they aren't nearly enough for the needs of all developers or all types of applications. Besides the built-in functions, you can write custom functions to simplify the development of your custom applications, as explained in the following section.

Custom Functions

Most of the code we write is in the form of custom functions or subroutines that are called from several places in the application. Subroutines are just like functions except that they don't return a value, so we'll focus on the implementation of custom functions. With the exception of a function's return value, everything else presented in this and the following section applies to subroutines as well.

Let's look at an example of a fairly simple (but not trivial) function that does something useful. Books are identified by a unique international standard book number (ISBN), and every application that manages books needs a function to verify the ISBN, which is made up of 12 digits followed by a check digit. To calculate the check digit, you multiply each of the 12 digits by a constant; the first digit is multiplied by 1, the second digit is multiplied by 3, the third digit by 1 again, the fourth digit by 3, and so on. The sum of these multiplications is then divided by 10, and we take the remainder. The check digit is this remainder subtracted from 10. To calculate the check digit for the ISBN 978078212283, compute the sum of the following products:

9 * 1 + 7 * 3 + 8 * 1 + 0 * 3 + 7 * 1 + 8 * 3 +
2 * 1 + 1 * 3 + 2 * 1 + 2 * 3 + 8 * 1 + 3 * 3 = 99

The sum is 99; when you divide it by 10, the remainder is 9. The check digit is 10 – 9, or 1, and the book's complete ISBN is 9780782122831. The ISBNCheckDigit() function, shown in Listing 3.11, accepts the 12 digits of the ISBN as an argument and returns the appropriate check digit.

Example 3.11. The ISBNCheckDigit() custom function

Function ISBNCheckDigit(ByVal ISBN As String) As String
    Dim i As Integer, chksum As Integer = 0
    Dim chkDigit As Integer
    Dim factor As Integer = 3
    For i = 0 To 11
        factor = 4 - factor
        chksum += factor * Convert.ToInt16(ISBN.SubString(i, 1))
    Next
    Return (10 - (chksum Mod 10)).ToString
End Function

The ISBNCheckDigit() function returns a string value because ISBNs are handled as strings, not numbers. (Leading zeros are important in an ISBN but are totally meaningless, and omitted, in a numeric value.) The SubString method of a String object extracts a number of characters from the string to which it's applied. The first argument is the starting location in the string, and the second is the number of characters to be extracted. The expression ISBN.SubString(i, 1) extracts one character at a time from the ISBN string variable. During the first iteration of the loop, it extracts the first character; during the second iteration, it extracts the second character; and so on.

The extracted character is a numeric digit stored as a character, which is converted to its numeric value and then multiplied by the factor variable value. The result is added to the chkSum variable. This variable is the checksum of the ISBN. After it has been calculated, we divide it by 10 and take its remainder (the Mod operator returns the remainder of this division), which we subtract from 10. This is the ISBN's check digit and the function's return value.

You can use this function in an application that maintains a book database to make sure all books are entered with a valid ISBN. You can also use it with a web application that allows viewers to request books by their ISBN. The same code will work with two different applications, and you can even pass it to other developers. Developers using your function don't have to know how the check digit is calculated, just how to call the function and retrieve its result. In Chapter 8, "Working with Objects," you'll learn how to package this function as a method so that other developers can use it without having access to your code. They will be able to call it to calculate an ISBN's check digit, but they won't be able to modify the function's code.

To test the ISBNCheckDigit() function, start a new project, place a button on the form, and enter the following statements in its Click event handler (or open the ISBN project in the folder with this chapter's sample projects at www.sybex.com/go/masteringvb2010):

Private Sub Button1_Click(ByVal sender As System.Object,
               ByVal e As System.EventArgs) Handles Button1.Click
   Console.WriteLine("The check Digit is " &
                      ISBNCheckDigit("978078212283"))
End Sub

After inserting the code of the ISBNCheckDigit() function and the code that calls the function, your code editor should look like Figure 3.1. You can place a TextBox control on the form and pass the Text property of the control to the ISBNCheckDigit() function to calculate the check digit.

Calling the ISBNCheckDigit() function

Figure 3.1. Calling the ISBNCheckDigit() function

A similar algorithm is used for calculating the check digit of credit cards: the Luhns algorithm. You can look it up on the Internet and write a custom function for validating credit card numbers.

Passing Arguments and Returning Values

So far, you've learned how to write and call procedures with a few simple arguments and how to retrieve the function's return value and use it in your code. This section covers a few advanced topics on argument-passing techniques and how to write functions that return multiple values, arrays of values, and custom data types.

Passing an Unknown Number of Arguments

Generally, all the arguments that a procedure expects are listed in the procedure's definition, and the program that calls the procedure must supply values for all arguments. On occasion, however, you might not know how many arguments will be passed to the procedure. Procedures that calculate averages or, in general, process multiple values can accept from a few to several arguments whose count is not known at design time. Visual Basic supports the ParamArray keyword, which allows you to pass a variable number of arguments to a procedure. There are situations where you might not know in advance whether a procedure will be called with two or two dozen arguments, and this is where the ParamArray comes in very handy because it allows you to pass an array with any number of arguments.

Let's look at an example. Suppose that you want to populate a ListBox control with elements. To add a single item to the ListBox control, you call the Add method of its Items collection as follows:

ListBox1.Items.Add("new item")

This statement adds the string new item to the ListBox1 control. If you frequently add multiple items to a ListBox control from within your code, you can write a subroutine that performs this task. The following subroutine adds a variable number of arguments to the ListBox1 control:

Sub AddNamesToList(ByVal ParamArray NamesArray() As Object)
   Dim x As Object
   For Each x In NamesArray
      ListBox1.Items.Add(x)
   Next x
End Sub

This subroutine's argument is an array prefixed with the keyword ParamArray. This array holds all the parameters passed to the subroutine. If the parameter array holds items of the same type, you can declare the array to be of the specific type (string, integer, and so on). To add items to the list, call the AddNamesToList() subroutine as follows:

AddNamesToList("Robert", "Manny", "Renee", "Charles", "Madonna")

If you want to know the number of arguments actually passed to the procedure, use the Length property of the parameter array. The number of arguments passed to the AddNamesToList() subroutine is given by the following expression:

NamesArray.Length

The following loop goes through all the elements of the NamesArray array and adds them to the list (this is an alternate implementation of the AddNamesToList subroutine):

Dim i As Integer
For i = 0 to NamesArray.Length
   ListBox1.Items.Add(NamesArray(i))
Next i

A procedure that accepts multiple arguments relies on the order of the arguments. To omit some of the arguments, you must use the corresponding comma. Let's say you want to call such a procedure and specify the first, third, and fourth arguments. The procedure must be called as follows:

ProcName(arg1, , arg3, arg4)

The arguments to similar procedures are frequently of equal stature, and their order doesn't make any difference. A function that calculates the mean or other basic statistics of a set of numbers, or a subroutine that populates a ListBox or ComboBox control, is prime candidates for this type of implementation. If the procedure accepts a variable number of arguments that aren't equal in stature, you should consider the technique described in the following section. If the function accepts a parameter array, the parameter array must be the last argument in the list, and none of the other parameters can be optional.

Named Arguments

You learned how to write procedures with optional arguments and how to pass a variable number of arguments to the procedure. The main limitation of the argument-passing mechanism, though, is the order of the arguments. By default, Visual Basic matches the values passed to a procedure to the declared arguments by their order (which is why the arguments you've seen so far are called positional arguments).

This limitation is lifted by Visual Basic's capability to understand named arguments. With named arguments, you can supply arguments in any order because they are recognized by name and not by their order in the list of the procedure's arguments. Suppose you've written a function that expects three arguments: a name, an address, and an email address:

Sub CreateContact(Name As String, Address As String, EMail As String)

Presumably, this subroutine creates a new contact with the specified data, but right now we're not interested in the implementation of the function, just how to call it. When calling this subroutine, you must supply three strings that correspond to the arguments Name, Address, and EMail, in that order. You can call this subroutine as follows:

CreateContact("Peter Evans", "2020 Palm Ave., Santa Barbara, CA 90000",
              "[email protected]")

However, there's a safer way. You can call it by supplying the arguments in any order by their names:

CreateContact(Address:= "2020 Palm Ave., Santa Barbara, CA 90000",
              EMail:= "[email protected]", Name:= "Peter Evans")

The := operator assigns values to the named arguments. Because the arguments are passed by name, you can supply them in any order.

To test this technique, enter the following subroutine declaration in a form's code:

Sub CreateContact(ByVal Name As String, ByVal Address As String,
                 ByVal EMail As String)
   Debug.WriteLine(Name)
   Debug.WriteLine(Address)
   Debug.WriteLine(EMail)
End Function

Then call the CreateContact() subroutine from within a button's Click event with the following statement:

Debug.WriteLine(
        CreateContact(Address:= "2020 Palm Ave., Santa Barbara, CA 90000",
                      Name:= "Peter Evans", EMail:= "[email protected]"))

You'll see the following in the Immediate window:

Peter Evans
2020 Palm Ave., Santa Barbara, CA 90000
[email protected]

The subroutine knows which value corresponds to which argument and can process them the same way that it processes positional arguments. Notice that the subroutine's definition is the same, whether you call it with positional or named arguments. The difference is in how you call the subroutine and not how you declare it.

Named arguments make code safer and easier to read, but because they require a lot of typing, most programmers don't use them. Besides, when IntelliSense is on, you can see the definition of the function as you enter the arguments, and this minimizes the chances of swapping two values by mistake.

Functions Returning Arrays

In addition to returning custom data types, VB 2010 functions can return arrays. This is an interesting possibility that allows you to write functions that return not only multiple values, but also any number of values.

In this section, we'll write the Statistics() function, similar to the CalculateStatistics() function you saw a little earlier in this chapter. The Statistics() function returns the statistics in an array. Moreover, it returns not only the average and the standard deviation, but the minimum and maximum values in the data set as well. One way to declare a function that calculates all the statistics is as follows:

Function Statistics(ByRef DataArray() As Double) As Double()

This function accepts an array with the data values and returns an array of Doubles. To implement a function that returns an array, you must do the following:

  1. Specify a type for the function's return value and add a pair of parentheses after the type's name. Don't specify the dimensions of the array to be returned here; the array will be declared formally in the function.

  2. In the function's code, declare an array of the same type and specify its dimensions. If the function should return four values, use a declaration like this one:

    Dim Results(3) As Double

    The Results array, which will be used to store the results, must be of the same type as the function — its name can be anything.

  3. To return the Results array, simply use it as an argument to the Return statement:

    Return(Results)
  4. In the calling procedure, you must declare an array of the same type without dimensions:

    Dim Statistics() As Double
  5. Finally, you must call the function and assign its return value to this array:

    Stats() = Statistics(DataSet())

Here, DataSet is an array with the values whose basic statistics will be calculated by the Statistics() function. Your code can then retrieve each element of the array with an index value as usual.

Overloading Functions

There are situations in which the same function must operate on different data types or a different number of arguments. In the past, you had to write different functions, with different names and different arguments, to accommodate similar requirements. The Framework introduced the concept of function overloading, which means that you can have multiple implementations of the same function, each with a different set of arguments and possibly a different return value. Yet all overloaded functions share the same name. Let me introduce this concept by examining one of the many overloaded functions that comes with the .NET Framework.

The Next method of the System.Random class returns a random integer value from −2,147,483,648 to 2,147,483,647. (This is the range of values that can be represented by the Integer data type.) We should also be able to generate random numbers in a limited range of integer values. To emulate the throw of a die, we want a random value in the range from 1 to 6, whereas for a roulette game we want an integer random value in the range from 0 to 36. You can specify an upper limit for the random number with an optional integer argument. The following statement will return a random integer in the range from 0 to 99:

randomInt = rnd.Next(100)

You can also specify both the lower and upper limits of the random number's range. The following statement will return a random integer in the range from 1,000 to 1,999:

randomInt = rnd.Next(1000, 2000)

To use the Random class in your code, you must create a variable of this type and then call its methods:

Dim rnd As New Math.Random
MsgBox(rnd.Next(1, 6))

The same method behaves differently based on the arguments we supply. The behavior of the method depends on the type of the arguments, the number of the arguments, or both. As you will see, there's no single function that alters its behavior based on its arguments. There are as many different implementations of the same function as there are argument combinations. All the functions share the same name, so they appear to the user as a single multifaceted function. These functions are overloaded, and you'll see how they're implemented in the following section.

If you haven't turned off the IntelliSense feature of the editor, as soon as you type the opening parenthesis after a function or method name, you'll see a yellow box with the syntax of the function or method. You'll know that a function, or a method, is overloaded when this box contains a number and two arrows. Each number corresponds to a different overloaded form, and you can move to the next or previous overloaded form by clicking the two little arrows or by pressing the arrow keys.

Let's return to the Min() function we implemented earlier in this chapter. The initial implementation of the Min() function is shown next:

Function Min(ByVal a As Double, ByVal b As Double) As Double
   Min = IIf(a < b, a, b)
End Function

By accepting Double values as arguments, this function can handle all numeric types. VB 2010 performs automatic widening conversions (it can convert Integers and Decimals to Doubles), so this trick makes the function work with all numeric data types. However, what about strings? If you attempt to call the Min() function with two strings as arguments, you'll get a compiler error. The Min() function just can't handle strings.

To write a Min() function that can handle both numeric and string values, you must write two Min() functions. All Min() functions must be prefixed with the Overloads keyword. The following statements show two different implementations of the same function, one for numbers and another one for strings:

Overloads Function Min(ByVal a As Double, ByVal b As Double) As Double
   Min = Convert.ToDouble(IIf(a < b, a, b))
End Function

Overloads Function Min(ByVal a As String, ByVal b As String) As String
   Min = Convert.ToString(IIf(a < b, a, b))
End Function

You need a third overloaded form of the same function to compare dates. If you call the Min() function, passing as an argument two dates as in the following statement, the Min() function will compare them as strings and return (incorrectly) the first date:

Debug.WriteLine(Min(#1/1/2011#, #3/4/2010#))

This statement is not even valid when the Strict option is on, so you clearly need another overloaded form of the function that accepts two dates as arguments, as shown here:

Overloads Function Min(ByVal a As Date, ByVal b As Date) As Date
   Min = Convert.ToDateTime(IIf(a < b, a, b))
End Function

If you now call the Min() function with the dates #1/1/2011# and #3/4/2010#, the function will return the second date, which is chronologically smaller than the first. Assuming that you have inserted the three forms of the Min() function in your code as shown in Figure 3.2, as soon you enter the name of the function, the IntelliSense box will display the first form of the function. Click the buttons with the arrows to see the other ones and select the appropriate form.

(Top) The implementation of three overloaded forms of a function. (Bottom) The three overloaded forms of the Min() function in the IntelliSense list.

Figure 3.2. (Top) The implementation of three overloaded forms of a function. (Bottom) The three overloaded forms of the Min() function in the IntelliSense list.

If you're wondering about the Convert.ToDateTime method, it's used because the IIf() function returns a value of the Object type. Each of the overloaded forms of the Min() function, however, has a specific type. If the Strict option is on (the recommended setting), you should make sure the function returns the appropriate type by converting the result of the IIf() function to the corresponding type, as shown in the preceding Min() examples.

VB2010 at Work: The OverloadedFunctions Project

Let's look into a more complicated overloaded function, which makes use of some topics discussed later in this book. The CountFiles() function that follows counts the number of files in a folder that meet certain criteria. The criteria could be the size of the files, their type, or the date they were created. You can come up with any combination of these criteria, but the following are the most useful combinations. (These are the functions I would use, but you can create even more combinations or introduce new criteria of your own.) The names of the arguments are self-descriptive, so I won't explain what each form of the CountFiles() function does.

CountFiles(ByVal minSize As Integer, ByVal maxSize As Integer) As Integer
CountFiles(ByVal fromDate As Date, ByVal toDate As Date) As Integer
CountFiles(ByVal type As String) As Integer
CountFiles(ByVal minSize As Integer, ByVal maxSize As Integer,
           ByVal type As String) As Integer
CountFiles(ByVal fromDate As Date, ByVal toDate As Date,
           ByVal type As String) As Integer

Listing 3.12 shows an implementation of these overloaded forms of the CountFiles() function. (I'm not showing all overloaded forms of the function; you can open the OverloadedFunctions project in the IDE and examine the code.) Because we haven't discussed file operations yet, most of the code in the function's body will be new to you — but it's not hard to follow. For the benefit of readers who are totally unfamiliar with file operations, I included a statement that prints in the Immediate window the type of files counted by each function. The Debug.WriteLine statement prints the values of the arguments passed to the function along with a description of the type of search it will perform. The overloaded form that accepts two integer values as arguments prints something like this:

You've requested the files between 1000 and 100000 bytes

The overloaded form that accepts a string as an argument prints the following:

You've requested the .EXE files

Example 3.12. The overloaded implementations of the CountFiles() function

Overloads Function CountFiles(
                   ByVal minSize As Integer, ByVal maxSize As Integer) As Integer
   Debug.WriteLine("You've requested the files between " &
                    minSize &  " and " & maxSize & " bytes")
   Dim files() As String
   files = System.IO.Directory.GetFiles("c:windows")
   Dim i, fileCount As Integer
   For i = 0 To files.GetUpperBound(0)
      Dim FI As New System.IO.FileInfo(files(i))
      If FI.Length >= minSize And FI.Length <= maxSize Then
         fileCount = fileCount + 1
      End If
   Next
   Return(fileCount)
End Function

Overloads Function CountFiles(
ByVal fromDate As Date, ByVal toDate As Date) As Integer
   Debug.WriteLine("You've requested the count of files created from " &
                     fromDate & " to " & toDate)
   Dim files() As String
   files = System.IO.Directory.GetFiles("c:windows")
   Dim i, fileCount As Integer
   For i = 0 To files.GetUpperBound(0)
      Dim FI As New System.IO.FileInfo(files(i))
      If FI.CreationTime.Date >= fromDate And
              FI.CreationTime.Date <= toDate Then
         fileCount = fileCount + 1
      End If
   Next
   Return(fileCount)
End Function

Overloads Function CountFiles(ByVal type As String) As Integer
   Debug.WriteLine("You've requested the " & type & " files")
   ' Function Implementation

End Function

Overloads Function CountFiles(
                   ByVal minSize As Integer, ByVal maxSize As Integer,
                   ByVal type As String) As Integer
   Debug.WriteLine("You've requested the " & type &
                   " files between " & minSize & " and " &
                   maxSize & " bytes")
   ' Function implementation
End Function

Overloads Function CountFiles(
                   ByVal fromDate As Date,
                   ByVal toDate As Date, ByVal type As String) As Integer
   Debug.WriteLine("You've requested the " & type &
                   " files created from " & fromDate & " to " & toDate)
   ' Function implementation
End Function

If you're unfamiliar with the Directory and File objects, focus on the statement that prints to the Immediate window and ignore the statements that actually count the files that meet the specified criteria. After reading the tutorial "Accessing Folders and Files," published at www.sybex.com/go/masteringvb2010, you can revisit this example and understand the statements that select the qualifying files and count them.

Start a new project and enter the definitions of the overloaded forms of the function on the form's level. Listing 3.12 is lengthy, but all the overloaded functions have the same structure and differ only in how they select the files to count. Then place a TextBox and a button on the form, as shown in Figure 3.3, and enter a few statements that exercise the various overloaded forms of the function (such as the ones shown in Listing 3.13) in the button's Click event handler.

Example 3.13. Testing the overloaded forms of the CountFiles() function

Private Sub Button1_Click(...) Handles Button1.Click
   TextBox1.AppendText(CountFiles(1000, 100000) &
                " files with size between 1KB and 100KB" & vbCrLf)
   TextBox1.AppendText(CountFiles(#1/1/2006#, #12/31/2006#) &
                " files created in 2006" & vbCrLf)
   TextBox1.AppendText(CountFiles(".BMP") & " BMP files" & vbCrLf)
   TextBox1.AppendText(CountFiles(1000, 100000, ".EXE") &
                " EXE files between 1 and 100 KB" & vbCrLf)
   TextBox1.AppendText(CountFiles(#1/1/2006#, #12/31/2007#, ".EXE") &
                " EXE files created in 2006 and 2007")
End Sub
The OverloadedFunctions project

Figure 3.3. The OverloadedFunctions project

The button calls the various overloaded forms of the CountFiles() function one after the other and prints the results on the TextBox control. From now on, I'll be omitting the list of arguments in the most common event handlers, such as the Click event handler, because they're always the same and they don't add to the readability of the code. In place of the two arguments, I'll insert an ellipsis to indicate the lack of the arguments.

Function overloading is used heavily throughout the language. There are relatively few functions (or methods, for that matter) that aren't overloaded. Every time you enter the name of a function followed by an opening parenthesis, a list of its arguments appears in the drop-down list with the arguments of the function. If the function is overloaded, you'll see a number in front of the list of arguments, as shown in Figure 3.4. This number is the order of the overloaded form of the function, and it's followed by the arguments of the specific form of the function. The figure shows all the forms of the CountFiles() function.

The overloaded forms of the CountFiles() function

Figure 3.4. The overloaded forms of the CountFiles() function

Documenting Functions

When working with overloaded functions and methods, you need as much help from the editor as possible because there are many arguments taken in total. You can document each argument of each overloaded form with a short description that will be displayed in the IntelliSense box as the user enters the argument values for the selected form, as shown in Figure 3.4. The same techniques apply to all functions, of course, not just to overloaded functions. While you can get by without documenting functions that are not overloaded, it's almost a necessity when working with overloaded functions. To document a function, enter three single quotes in an empty line of the editor, just before the function's definition. As soon as you type the third quote, the editor will insert a boilerplate for the function as follows:

''' <summary>
'''
''' </summary>
''' <param name="fromDate"></param>
''' <param name="toDate"></param>
''' <param name="type"></param>
''' <returns></returns>
''' <remarks></remarks>

Enter any comments about the function in the summary section, even notes to yourself about future improvements, desired but not implemented features, and so on. There's a param section for each of the arguments where you must insert a short description regarding each argument. This is the description that will appear in the IntelliSense drop-down list as the user enters each argument. Finally, in the returns section you must enter the function's description, which will be also displayed in the IntelliSense list. Here's the documentation of one of the overloaded forms of the CountFiles method:

''' <summary>
'''
''' </summary>
''' <param name="minSize">The minimum size of the file to be included
    in the search</param>
''' <param name="maxSize">The maximum size of the file to be included
    in the search</param>
''' <param name="type">The number of files of the specified type</param>
''' <returns>The number of files with a size in a given range
    and of a specific type</returns>
''' <remarks></remarks>

The Bottom Line

Use Visual Basic's flow-control statements

Visual Basic provides several statements for controlling the flow of control in a program: decision statements, which change the course of execution based on the outcome of a comparison, and loop statements, which repeat a number of statements while a condition is true or false.

Master It

Explain briefly the decision statements of Visual Basic.

Write subroutines and functions

To manage large applications, break your code into small, manageable units. These units of code are the subroutines and functions. Subroutines perform actions and don't return any values. Functions, on the other hand, perform calculations and return values. Most of the language's built-in functionality is in the form of functions.

Master It

How will you create multiple overloaded forms of the same function?

Pass arguments to subroutines and functions

Procedures and functions communicate with one another via arguments, which are listed in a pair of parentheses following the procedure's name. Each argument has a name and a type. When you call a procedure, you must supply values for each argument, and the types of the values should match the types listed in the procedure's definition.

Master It

Explain the difference between passing arguments by value and passing arguments by reference.

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

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