If Then is well suited for making a decision based on the evaluation of a single condition, and it's the most commonly used decision construct. If Then has the following syntax:
If condition Then statement
or
If condition Then [statements] End If
If condition evaluates to True, the statement or statements are executed. If condition evaluates to False, the statement or statements are not executed. Expressions can be simple or complex. Regardless of complexity, the expression used for condition must evaluate to True or False. For instance, all the following expressions are valid for condition.
sngCoordinate >= 8 strFirstName = "Adam" blnInHere
Although at first glance the last item doesn't appear to be a condition, remember that a condition always evaluates to either True or False. Because blnInHere refers to a Boolean variable (as denoted by its prefix), blnInHere inherently evaluates to True or False and is therefore a valid condition.
Note
If condition evaluates to a numerical value, Microsoft Visual Basic interprets the result as True or False; a zero value is considered False, and all nonzero numeric values are considered True.
To execute a statement or a set of statements when condition evaluates to False, use an Else statement. All statements between Else and End If are executed when condition evaluates to False. All statements between If Then and Else execute only if condition evaluates to True; never do both sets of statements execute in a single pass through an If End If construct.
If only one statement executes when condition evaluates to True, the statement can be put on the same line as If and End If can be omitted. However, to make the code more legible, you might consider placing the statement on its own line and closing the construct with End If.
' If locking conflicts were encountered, build this information ' into the message string. If lngLockingConflicts > 0 Then strMessage = strMessage & ", " & _ lngLockingConflicts & _ " contact(s) could not be added " & _ "due to locking conflicts!"
' If locking conflicts were encountered, build this information ' into the message string. If lngLockingConflicts > 0 Then strMessage = strMessage & ", " & lngLockingConflicts & _ " contact(s) could not be added due to " & _ "locking conflicts!" End If
When creating an If Then decision structure, it's possible to create a compound condition composed of multiple smaller conditions. Consider the following code:
If Len(rstContacts.Source) > 0 And Not (rstContacts.EOF) Then rstContacts.Delete() End If
This decision structure attempts to delete a record if the Recordset's EOF (end-of-file) property is False. First, the If statement makes sure that the Recordset's source has been set, and then it makes sure that the EOF property is False. This code, however, has the potential to generate a run-time error.
If rstContacts doesn't have its Source set (Len(rstContacts.Source) = 0), you might think that Visual Basic wouldn't evaluate the second part of the condition, which is rstContacts.EOF. With an And compound condition, if either of the individual conditions evaluates to False, the entire condition evaluates to False. Therefore, with the first part of the compound condition being False, there's really no need to evaluate the second part. Unfortunately, this is not how Visual Basic behaves: Visual Basic evaluates each component of a compound condition regardless of whether it's necessary to do so. In this example, rstContacts indeed doesn't have its Source set, and attempting to determine whether the Recordset's EOF property is True causes a run-time error.
Fortunately, Visual Basic .NET introduced two new comparison operators that perform the evaluation of expressions (from left to right) in a compound condition only until the value of the compound condition is clear. The remaining individual conditions aren't evaluated and therefore cannot cause run-time exceptions. This behavior is called short-circuiting. To short-circuit the evaluation of an expression, you have to use one of the two new comparison operators: AndAlso (use in place of And) and OrElse (use in place of Or).
Private Function FirstContactsState() As String ' Purpose : Return the state of the first contact in the ' Contacts table. ' Returns : The state of the first contact, otherwise an empty ' string. Dim strSQL As String Dim rstContacts As New ADODB.Recordset() ' Retrieve the contacts, ordered by their name. strSQL = "SELECT * FROM Contacts ORDER BY [ContactName];" ' Create the recordset of contacts. rstContacts.Open(strSQL, m_cnADOConnection, _ ADODB.CursorTypeEnum.adOpenDynamic, _ ADODB.LockTypeEnum.adLockOptimistic) ' Make sure we're not at the end of file, and that the first ' contact has a state. If Not (rstContacts.EOF) And _ rstContacts.Fields("ContactState").Value <> "" Then FirstContactsState = rstContacts.Fields("ContactState").Value End If End Function
Private Function FirstContactsState() As String ' Purpose : Return the state of the first contact in the ' Contacts table. ' Returns : The state of the first contact, otherwise an empty ' string. Dim strSQL As String Dim rstContacts As New ADODB.Recordset() ' Retrieve the contacts, ordered by their name. strSQL = "SELECT * FROM Contacts ORDER BY [ContactName];" ' Create the recordset of contacts. rstContacts.Open(strSQL, m_cnADOConnection, _ ADODB.CursorTypeEnum.adOpenDynamic, _ ADODB.LockTypeEnum.adLockOptimistic) ' Make sure we're not at the end of file, and that the first ' contact has a state. If Not (rstContacts.EOF) AndAlso _ rstContacts.Fields("ContactState").Value <> "" Then FirstContactsState = rstContacts.Fields("ContactState").Value End If End Function
When evaluating an expression that has only two possible values (True or False), If Then is the best decision construct to use. If you must take some action when the expression evaluates to False as well as when it evaluates to True, add an Else clause. Because there's no easy way to use If Then to compare an expression to more than two possible values, Select Case becomes the best choice in that situation. A typical Select Case construct has the following syntax:
Select Case testexpression [Case expressionlist1 [statementblock-1]] [Case expressionlist2 [statementblock-2]] [Case Else [statementblock-n]] End Select
Note
Select Case can be used in many advanced ways, including putting multiple result values on a single Case line. My intent in this chapter is to show you when to use it and how to use it properly, not to show you the many advanced ways it can be employed.
At times, you might find that you want to perform an action only when a given condition is True. If the condition evaluates to False, you want to evaluate a second condition and execute code based on the results of this new condition. You can create complex decision structures by using this method. The following shows a skeleton of just such a decision construct:
If condition1 Then ElseIf condition2 Then ElseIf condition3 Then Else End If
Notice that these conditions can be entirely unrelated. This is in contrast to the Select Case structure where the conditions are formed by successively comparing one (typically non-Boolean) test expression to each of the expressions in the Case statements. If you find that you're evaluating the same expression and comparing it to a variety of possible values, you should use Select Case rather than If Then ElseIf End If.
' Set the pushed state of the proper option button. If rstTask.Fields("Type").Value = "Phone Call" Then optPhoneCall.Checked = True ElseIf rstTask.Fields("Type").Value = "Appointment" Then optAppointment.Checked = True ElseIf rstTask.Fields("Type").Value = "To-do" Then optTodo.Checked = True End If
' Set the pushed state of the proper option button. Select Case rstTask.Fields("Type").Value Case Is = "Phone Call" optPhoneCall.Checked = True Case Is = "Appointment" optAppointment.Checked = True Case Is = "To-do" optTodo.Checked = True Case Else ' No other values are expected. End Select
Tip
You can use Select Case in ways that might not be immediately apparent. For instance, when you have a number of option buttons on a form, you often need to determine which of the option buttons is selected (Checked = True). If you consider True as your test expression, you can create a fairly nifty construct to determine which option button is selected:
' Determine which option button is selected. Select Case True Case Is = optDragCopyFile.Checked Case Is = optDragMoveFile.Checked Case Is = optDragCreateShortcut.Checked Case Else ' There are no further option buttons to test. End Select
Generally, you design a Select Case construct such that it handles every possible value of testexpression. Case Else is useful for creating code to execute when none of your specifically expected results are encountered. However, many developers often leave out Case Else if they've included Case statements for all expected results. In general, it's a good idea to always include Case Else. If you want to ignore the results not specifically accounted for in the Case statements, simply note this in a comment in the Case Else clause. If you firmly believe that no value would cause Case Else to execute that is, if all possible results are accounted for in the Case statements consider raising an error in the Case Else clause. That way, if a value slips through which could happen if additional possible results are created during future development and the Select Case structure is not properly updated you'll know about it. Always including a Case Else clause makes your code more self-documenting, and you don't force other developers to guess your intentions for handling results not specifically accounted for in Case statements.
Select Case m_intActiveSearchType Case Is = c_ListSearch txtSearch.Text = grdListFind.Columns(c_ListFindName) Case Is = c_PhoneSearch txtSearch.Text = grdPhones.Columns(c_PhoneFindName) Case Is = c_PriceBookSearch txtSearch.Text = grdPriceBook.Columns(c_PriceBookFindName) End Select
Select Case m_intActiveSearchType Case Is = c_ListSearch txtSearch.Text = grdListFind.Columns(c_ListFindName) Case Is = c_PhoneSearch txtSearch.Text = grdPhones.Columns(c_PhoneFindName) Case Is = c_PriceBookSearch txtSearch.Text = grdPriceBook.Columns(c_PriceBookFindName) Case Else ' All possible values should be covered, but just in case... MessageBox.Show("Unexpected value encountered for " & _ "m_intActiveSearchType in " & _ Me.Name & ": " & CStr(m_intActiveSearchType), _ "Error", MessageBoxButtons.OK, _ MessageBoxIcon.Exclamation) End Select
The order of the various Case statements in a Select Case construct might seem superficial, but it's often quite important. When ordering the statements, consider speed and readability. When a Select Case statement is encountered, the Case statements are evaluated in their listed order until a condition is found to be True. In a large list of items, and when speed is the primary concern, you might consider putting the most frequently expected values at the top of the Case list. More often than not, however, speed is second in importance to readability and ease of maintenance. In these cases, put the list of items in alphabetical or numerical order, which makes it easier to debug the code and to add new values to the Case list.
Select Case rstBilling.Fields("Basis").Value Case Is = "Metered" Call ComputeMeteredContract() Case Is = "Hourly" Call ComputeHourlyContract() Case Is = "Units" Call ComputeUnitsContract() Case Is = "Incidents" Call ComputeIncidentsContract() End Select
Select Case rstBilling.Fields("Basis").Value Case Is = "Hourly" Call ComputeHourlyContract() Case Is = "Incidents" Call ComputeIncidentsContract() Case Is = "Metered" Call ComputeMeteredContract() Case Is = "Units" Call ComputeUnitsContract() Case Else ' No other values are expected. End Select
When creating Select Case structures that evaluate a numeric value, you can create Case statements that never produce a True result. This usually occurs as a result of incorrectly ordering the Case statements, causing an earlier statement to evaluate to True before a later statement is encountered. Notice how the Case Is <= 0 statement in the incorrect code below never evaluates to True. A value of less than 0 causes the preceding Case statement (Case Is <= 5) to evaluate to True, stopping all further evaluations.
Select Case sngTaxRate Case Is <= 5 Case Is <= 0 Case Is <= 10 Case Else End Select
Select Case sngTaxRate Case Is <= 0 Case Is <= 5 Case Is <= 10 Case Else End Select
Chapter 7, "Commenting Code," describes the proper way to comment a program in great detail. Although Chapter 7 argues that inline comments are superior to end-of-line comments, end-of-line comments are appropriate at times for example, when nested decision structures significantly complicate code. In long procedures, it can be difficult to determine which end-of-construct statement (End Select or End If) corresponds to which beginning-of-construct statement. In these situations, use an end-of-line comment after the last statement of each decision structure to state which decision construct the closing statement belongs to.
Note
If you prefer, you can use end-of-line comments after the terminating statements of all your decision structures, regardless of whether they're part of a nested group.
' Make sure the user has entered a positive number of hours. If otnumHours.Value <= 0 Then MessageBox.Show("This is an hourly contract. You must enter " & _ "a positive number of hours.", _ MessageBoxButtons.OK, MessageBoxIcon.Information) otnumHours.Focus() Else ' Determine whether the user has entered a rate. If otnumRate.Value > 0 Then otnumPrice.Value = otnumHours.Value * otnumRate.Value Else If otnumPrice.Value > 0 Then ' No rate entered; check whether a contract price is set. ' If a contract price is set, figure the rate based on the ' hours entered. otnumRate.Value = otnumPrice.Value / otnumHours.Value End If End If End If
' Make sure the user has entered a positive number of hours. If otnumHours.Value <= 0 Then MessageBox.Show("This is an hourly contract. You must enter " & _ "a positive number of hours.", _ MessageBoxButtons.OK, MessageBoxIcon.Information) otnumHours.Focus() Else ' Determine whether the user has entered a rate. If otnumRate.Value > 0 Then otnumPrice.Value = otnumHours.Value * otnumRate.Value Else If otnumPrice.Value > 0 Then ' No rate entered; check whether a contract price is set. ' If a contract price is set, figure the rate based on the ' hours entered. otnumRate.Value = otnumPrice.Value / otnumHours.Value End If ' otnumPrice.Value > 0 End If ' otnumRate.Value > 0 End If ' otnumHours.Value <= 0
The evaluation of expressions is at the heart of creating decision structures. Most expressions can be written in multiple ways. Properly formatting an expression reduces the possibility of errors in your code and increases its readability. Because most expressions used in decision structures evaluate to a Boolean value (True or False), correctly working with Boolean expressions is crucial.
This seems like a basic principle, but it's one that is often violated. Boolean values are True or False, so there's no need to compare them to True or False. The incorrect code below came from a well-respected Visual Basic magazine. Lack of a naming convention aside, this procedure suffers from a case of overcomplication. BOF and EOF are properties of the Recordset object that indicate that the Recordset is at beginning-of-file or end-of-file, respectively. Each can be either True or False. Because they are inherently Boolean values, comparing them directly to True or False makes the code cluttered and can decrease performance.
Public Function IsEmptyRecordset(ByRef rs As Recordset) As Boolean IsEmptyRecordset = ((rs.BOF = True) And (rs.EOF = True)) End Function
Public Function IsEmptyRecordset(ByRef rs As Recordset) As Boolean IsEmptyRecordset = rs.BOF And rs.EOF End Function
A classic case of overcomplicating a procedure is creating a Boolean variable name that reflects the negative of some condition. Basing decisions on such variables adds an unnecessary layer of complexity for instance, why call a variable blnNotLoaded when blnLoaded works just as well and is easier for the mind to deal with? When you work with the negative, you increase the chances for errors in your code because you might not catch problems as you write them. This isn't not like using double negatives in a sentence get it? If you must deal with the negative, use Not on the positive form of the variable rather than using the negative form of the variable.
Dim blnInvalidTemplate As Boolean ' Attempt to open the template. The function OpenTemplate returns ' success or failure. blnInvalidTemplate = Not (OpenTemplate(strTemplateName)) ' If the template is invalid, get out. If blnInvalidTemplate Then GoTo PROC_EXIT End If
Dim blnValidTemplate As Boolean ' Attempt to open the template. The function OpenTemplate returns ' success or failure. blnValidTemplate = OpenTemplate(strTemplateName) ' If the template is invalid, get out. If Not (blnValidTemplate) Then GoTo PROC_EXIT End If
Parentheses are used in algebraic expressions to override the traditional order of operations. For instance, standard order of operations (order of precedence) dictates that multiplication takes place before addition. So, the statement Debug.WriteLine(1 + 5 * 6) prints the value 31. To override this behavior, you use parentheses. Items in parentheses are evaluated first. For instance, Debug.WriteLine((1 + 5) * 6) prints 36. Although you don't have to provide parentheses if you want to use the traditional order of operations, you should use them anyway to add clarity to complicated expressions.
' Compute the height and width of the editable area. m_sngEditWidth = m_sngImageWidth * m_intMagnification + 1 m_sngEditHeight = m_sngImageHeight * m_intMagnification + 1
' Compute the height and width of the editable area. m_sngEditWidth = (m_sngImageWidth * m_intMagnification) + 1 m_sngEditHeight = (m_sngImageHeight * m_intMagnification) + 1
When writing decision structures, make the flow of the code as obvious as possible. Pen and paper shouldn't be required for someone to figure out your intentions. The incorrect code below was also culled from a well-known Visual Basic publication. Can you easily tell exactly what's happening here? The code is setting the Cancel parameter equal to the result of the MsgBox function. No the return value of the function is compared to vbCancel, and the result (True or False) is what's stored in the Cancel parameter. Why make the reader work so hard? The revised code performs the same function without any real loss of performance, and it's much easier to understand.
Private Sub fclsMain_Closing(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) _ Handles MyBase.Closing ' Get confirmation before closing the form. e.Cancel = (MsgBox("Quit Now?", vbOKCancel Or _ vbQuestion, "Confirmation Demo") = vbCancel) End Sub
Private Sub fclsMain_Closing(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) _ Handles MyBase.Closing ' Get confirmation before closing the form. If MsgBox("Quit Now?", vbOKCancel Or vbQuestion, _ "Confirmation Demo") = vbCancel Then e.Cancel = True End If End Sub
Although the venerable GoTo statement has been used to force execution to a specific line in a procedure for quite some time, GoTo statements make code difficult to follow because they often redirect the execution path in unintuitive ways. GoTo used to be perfect for jumping to a single exit point. However, the new Try Catch Finally construct now gives you structured exception handling and single exit points all rolled into one. In general, there's almost always a better way to write a process than by using GoTo. Rarely, if ever, should a GoTo statement send code execution back in a procedure. Often, when a GoTo statement sends execution backward, some sort of standard looping construct would be a better solution. The following code illustrates how code with a GoTo statement can be better written as a Do loop.
Private Function StripDoubleQuotes(ByVal strString As String) As String ' Purpose : Locate all double quotes within a string and change ' them to single quotes. ' Accepts : strString - the string in which to search for ' double quotes. ' Returns : The string passed here as strString, with double ' quotes replaced by single quotes. Dim intLocation As Integer Const c_SingleQuote = "'" START_CHECK: ' Look for a double quote. intLocation = InStr(strString, ControlChars.Quote) ' If a double quote is found, replace it with a single quote. If intLocation > 0 Then ' A double quote has been found. Replace it with ' a single quote. Mid$(strString, intLocation, 1) = c_SingleQuote GoTo START_CHECK End If PROC_EXIT: ' No more double quotes were found. Return the new string. Return strString End Function
Private Function StripDoubleQuotes(ByVal strString As String) As String ' Purpose : Locate all double quotes within a string and change ' them to single quotes. ' Accepts : strString - the string in which to search for ' double quotes. ' Returns : The string passed here as strString, with double ' quotes replaced by single quotes. Dim intLocation As Integer Const c_SingleQuote = "'" Do ' Look for a double quote. intLocation = InStr(strString, ControlChars.Quote) ' If a double quote is found, replace it with a single quote. If intLocation > 0 Then ' A double quote has been found. Replace it with ' a single quote. Mid$(strString, intLocation, 1) = c_SingleQuote End If Loop While intLocation > 0 PROC_EXIT: ' No more double quotes were found. Return the new string. Return strString End Function
GoTo statements make code hard to read. Reduce the amount of effort required to scan a procedure for GoTo labels by using all uppercase letters for those labels.
Private Sub DoSomething() ' Do something here. Proc_Exit: End Sub
Private Sub DoSomething() ' Do something here. PROC_EXIT: End Sub