Directives

[Previous] [Next]

10.1 Use For Next to loop a specific number of times.

The most basic type of loop is the For Next loop, which loops a specific number of times. When the maximum number of iterations is reached, execution continues with the statement following the Next statement. When you create a For Next loop, you designate the starting number to use for counting as well as the ending number. You can also specify an increment by using the Step keyword.

The For Next construct has the following syntax:

  For   counter   =   start   To   end  [  Step   step  ] [  statements  ] [  ExitFor  ] [  statements  ]  Next  [  counter  ] 

The For Next construct has these parameters:

  • counter A required numeric variable used as a loop counter. The variable can't be a Boolean or an array element.
  • start A required initial value of counter.
  • end A required final value of counter . When counter reaches the end number, the statements within the loop are executed one last time and then execution continues with the line following the Next statement. If counter exceeds the end number, execution continues with the line following the Next statement.
  • step An optional number that specifies the amount counter is incremented each time through the loop. If you omit step , the counter variable is incremented by 1.

Most of the programming work in a For Next loop is done in the For statement that starts the loop. The majority of For Next loops are fairly simple ”the loop counter proceeds from one number to another by a step of 1. When you want a step of 1, you should omit the Step component. For instance, the following statement loops from 1 to 10 with a step of 1:

  Dim  intCounter  AsInteger For  intCounter=1  To  10   Next  intCounter 

To increment the counter by a step other than 1, you must supply the Step component in the For statement. For example, to loop from 1 to 10 with a step of 2, you can use this For statement:

  Dim  intCounter  AsInteger For  intCounter=1  To  10  Step  2  Debug.Print  intCounter  Next  intCounter 

To create loops that perform as you expect them to, you must fully understand the Step component. If you were asked to count to 10 by 2s, you'd probably count as follows :

 2,4,6,8,10 

However, the code shown above actually prints the following in the debug window:

 1,3,5,7,9 

When you start a loop with a For statement, the counter variable is initialized to the start value. In this example, the start value is 1. Once the loop executes, the counter variable is incremented (or decremented, in the case of a negative step value) by the value of step . This continues until the value of the counter variable exceeds the end value. When the counter variable reaches 9 and the loop completes, the counter variable is incremented to 11. Since 11 exceeds the end value of 10, the loop does not repeat again. Some developers don't understand this behavior. For example, what value is printed to the debug window when the following code executes?

  Dim  intCounter  AsInteger   '* Loop10times.   For  intCounter=1  To  10   Next  intCounter  Debug.Print  intCounter 

If your first instinct is to say 10, you're mistaken but you're not alone. When the Next statement is reached and intCounter is 9, intCounter is incremented to 10 and the loop executes again. When the Next statement is encountered while intCounter has a value of 10, the loop does not immediately terminate. Instead, intCounter is incremented to 11 and execution again returns to the For statement. When the For statement evaluates intCounter and finds that it exceeds the end value, code execution moves to the statement immediately following the Next statement, which prints the value of intCounter . The value of intCounter that prints is 11.

NOTE
In Chapter 5, "Using Constants and Enumerations," I discussed why you should eliminate magic (hard-coded) numbers in favor of constants. Loops are good places to look for opportunities to use constants.

Practical Application 10.1.1 shows how a procedure is more readable when you replace hard-coded numbers in a For statement with constants. In addition to magic numbers and constants, you can also use variables or control values for the start and end parameters. For instance, the following code uses the value of a variable for the upper limit:

  Dim  intCounter  AsInteger Dim  intUpperLimit  AsInteger   '* Initializeupperlimit.Thisvaluecancomefromanywhere, '* suchasfromacontrolonaformorfromatextfile.  intUpperLimit=100  For  intCounter=1  To  intUpperLimit   Next  intCounter 

When you use variable data for the start and end values in a For Next loop, you must use valid values. For instance, the following procedure is the same as the previous one, with one small but important change: intUpperLimit is set to -5.

  Dim  intCounter  AsInteger Dim  intUpperLimit  AsInteger   '* Initializeupperlimit.Thisvaluecancomefromanywhere, '* suchasfromacontrolonaformorfromatextfile.  intUpperLimit=-5  For  intCounter=1  To  intUpperLimit   Next  intCounter 

This loop doesn't execute at all. When the For statement is first encountered, intCounter is initialized to 1 and compared to intUpperLimit . Since it has a value greater than that of intUpperLimit , code execution moves to the statement immediately following the Next statement. In some processes, this might be acceptable behavior; in others, it might not.

Every For statement must have a corresponding Next statement. You technically don't have to include the counter variable in the Next statement, but you should. Omitting the counter variable isn't much of a problem in a simple procedure with only one loop. But as a procedure grows in size and complexity or as nested loops are created, it becomes much more important that the counter variable be used. For example, look at these nested loops:

  Dim  intCounter  AsInteger Dim  intSecondCounter  AsInteger   '* Createtheouterloop.   For  intCounter=1  To  100  '* Createanestedloopalongwithanewcounter.   For  intSecondCounter=1  To  100  '* Printthevalueofthesecondcounter.   Debug.Print  intSecondCounter  Next Next  

This example has a loop within a loop. Because the counter variables are omitted from the Next statements, it's less clear which Next statement is closing which loop. If there were many statements between the For and Next statements, it would be even more difficult to discern the closing of the loops.

You should indent all statements between the For and Next statements at least one tab stop to make the structure of the code apparent. (Chapter 8, "Formatting Code," discusses indentation in detail, but it's also worthwhile describing its importance here.) Indentation helps to display the flow or structure of code. Loops have a definite structure ”a beginning, a body, and an end. The beginning and end statements appear at the same level in the hierarchy, with the body statements subordinate. The first statement after a For statement must be indented one tab stop. The indentation of the rest of the body statements depends on their relationship to this first statement.

While most For Next loops are designed to loop a designated number of times, sometimes you need to end a For Next loop prematurely. Many developers make the mistake of using GoTo and a label to exit a loop, as shown in Practical Application 10.1.5. The correct method of leaving a For Next loop is to use the Exit For statement:

  Dim  intCounter  AsInteger   '* Loopthroughthepossibledaysofthemonth.   For  intCounter=1  To  31  '* Ifthecounterisequaltothecurrentdayofthemonth, '* exittheloop.   If  intCounter=Format(Date,"d")  ThenExitFor   Debug.Print  intCounter  Next  intCounter 

The preceding code is not particularly functional, but it illustrates a point. If you must exit the loop early, do it with the Exit For statement.

Incorrect:

  PrivateFunction  RawNumber(strNumber  AsString  )  AsString   '* Purpose:Stripsanalphanumericstringdowntojust '* itsnumericcomponenttobeusedforfaxing. '* Accepts:strNumber-astringcontainingaphonenumber, '* e.g.,(402)555-1212. '* Returns:Thestringwithallnonnumericelementsremoved.   OnErrorGoTo  PROC_ERR  Dim  strRaw  AsString   Dim  intLocation  AsInteger  intLocation=1  '* Loopthroughallthecharactersinthestring.   DoWhile  intLocation<=Len(strNumber)  '* Determinewhetherthecurrentcharacterisnumeric.   If  IsNumeric(Mid$(strNumber,intLocation,1))  Then  strRaw=strRaw&Mid$(strNumber,intLocation,1)  EndIf  intLocation=intLocation+1  Loop  RawNumber=strRaw PROC_EXIT:  ExitFunction  PROC_ERR:  Call  ShowError(Me.Name,"RawNumber",Err.Number,Err.Description)  GoTo  PROC_EXIT  EndFunction  

Correct:

  PrivateFunction  RawNumber(strNumber  AsString  )  AsString   '* Purpose:Stripsanalphanumericstringdowntojust '* itsnumericcomponenttobeusedforfaxing. '* Accepts:strNumber-astringcontainingaphonenumber, '* e.g.,(402)555-1212. '* Returns:Thestringwithallnonnumericelementsremoved.   OnErrorGoTo  PROC_ERR  Dim  strRaw  AsString   Dim  intLocation  AsInteger   '* Loopthroughallthecharactersinthestring.   For  intLocation=1  To  Len(strNumber)  '* Determinewhetherthecurrentcharacterisnumeric.   If  IsNumeric(Mid$(strNumber,intLocation,1))  Then  strRaw=strRaw&Mid$(strNumber,intLocation,1)  EndIf   Next  intLocation RawNumber=strRaw PROC_EXIT:  ExitFunction  PROC_ERR:  Call  ShowError(Me.Name,"RawNumber",Err.Number,Err.Description)  GoTo  PROC_EXIT  EndFunction  

Practical Applications

10.1.1 Use a constant for step whenever possible. Since the value of step is always numeric and its meaning varies from process to process, you should use a named constant rather than a literal value.

Incorrect:

  '* Drawverticalgridlinesonthecanvas.   For  intCount=0  To  (intEditWidth+1)  Step  4  '* UsetheAPItomovethedrawingpen.  lngResult=MoveToEx(lngScratchPadDC,intCount,0,  ByVal  0&)  '* Drawastraightline.  lngResult=LineTo(lngScratchPadDC,intCount,intEditHeight)  Next  intCount 

Correct:

  Const  c_Magnification=4  '* Drawverticalgridlinesonthecanvas.   For  intCount=0  To  (intEditWidth+1)  Step  c_Magnification  '* UsetheAPItomovethedrawingpen.  lngResult=MoveToEx(lngScratchPadDC,intCount,0,  ByVal  0&)  '* Drawastraightline.  lngResult=LineTo(lngScratchPadDC,intCount,intEditHeight)  Next  intCount 

10.1.2 Avoid referencing the counter variable after a loop ends. When a For Next loop ends, the final value of the counter variable is not equal to the value of end ”it's higher or lower based on the value of step . If you expect a loop to run its full number of times, don't use the counter variable after the loop ends.

Although the following procedure marked as incorrect appears acceptable, it contains a nasty bug. When this procedure executes, an error is generated on the statement that attempts to print in the debug window. The error is Error 9, Subscript out of range , and it occurs because intCounter is actually 11 when the loop terminates. (The last element in the array has an index of 10.)

Incorrect:

  PrivateSub  FillArray()  '* Purpose:Fillanarraywithvalues,andprintthe '* valueinthelastelement.   OnErrorGoTo  PROC_ERR  Dim  intCounter  AsInteger   Dim  aintArray(1  To  10)  AsInteger   '* Initializetherandomnumbergenerator.  Randomize  '* Populatethearrayelementswithrandomnumbers.   For  intCounter=1  To  10 aintArray(intCounter)=(10*Rnd)+1  Next  intCounter  '* Printthevalueofthelastelement.   Debug.Print  aintArray(intCounter) PROC_EXIT:  ExitSub  PROC_ERR:  Call  ShowError(Me.Name,"FillArray",Err.Number,Err.Description)  GoTo  PROC_EXIT  EndSub  

Correct:

  PrivateSub  FillArray()  '* Purpose:Fillanarraywithvaluesandprintthe '* valueinthelastelement.   OnErrorGoTo  PROC_ERR  Dim  intCounter  AsInteger   Const  c_ArrayMax=10  Const  c_ArrayMin=1  Dim  aintArray(c_ArrayMin  To  c_ArrayMax)  AsInteger   '* Initializetherandomnumbergenerator.  Randomize  '* Populatethearrayelementswithrandomnumbers.   For  intCounter=c_ArrayMin  To  c_ArrayMax aintArray(intCounter)=(c_ArrayMax*Rnd)+1  Next  intCounter  '* Printthevalueofthelastelement.   Debug.Print  aintArray(c_ArrayMax) PROC_EXIT:  ExitSub  PROC_ERR:  Call  ShowError(Me.Name,"FillArray",Err.Number,Err.Description)  GoTo  PROC_EXIT  EndSub  

10.1.3 Include the counter variable in all Next statements. Code will still execute if you omit the counter variable from the Next statement, but it will be less readable and therefore more difficult to maintain.

Incorrect:

  Const  c_MinPort=1  Const  c_MaxPort=4  '* SettheCommportbasedontheselectedoptionbutton.   For  intCounter=c_MinPort  To  c_MaxPort  '* IfaCommportoptionbuttonisselected,usethatCommport '* andexittheloop.   If  optCommPort(intCounter).value=True  Then  intCommPort=intCounter  ExitFor EndIf Next  

Correct:

  Const  c_MinPort=1  Const  c_MaxPort=4  '* SettheCommportbasedontheselectedoptionbutton.   For  intCounter=c_MinPort  To  c_MaxPort  '* IfaCommportoptionbuttonisselected,usethatCommport '* andexittheloop.   If  optCommPort(intCounter).value=True  Then  intCommPort=intCounter  ExitFor EndIf Next  intCounter 

10.1.4 Indent body statements in a For Next loop for visual clarity. Whenever you have a code construct with a beginning and ending statement, you have a situation that calls for indentation.

Incorrect:

  '* Fillthedateselectioncombobox.   For  intYear=1969  To  2020 cboYear.AddItemStr(intYear)  Next  intYear 

Correct:

  '* Fillthedateselectioncombobox.   For  intYear=1969  To  2020 cboYear.AddItemStr(intYear)  Next  intYear 

10.1.5 If you must exit a For Next loop early, use an Exit For statement. Avoid using GoTo and a label to exit a loop.

Incorrect:

  '* Selectthe"unassigned"categorynodeintheTreeviewcontrol.   For  lngIndex=1  To  treCategory.Nodes.Count  '* Checktoseewhetherthisisthe"unassigned"node.   If  treCategory.Nodes(lngIndex).Text=c_strUnassigned  Then  treCategory.Nodes(lngIndex).Selected=  True   '* Noneedtokeeplooking;getoutoftheloop.   GoTo  END_OF_LOOP  EndIf Next  lngIndex END_OF_LOOP: 

Correct:

  '* Selectthe"unassigned"categorynodeintheTreeviewcontrol.   For  lngIndex=1  To  treCategory.Nodes.Count  '* Checktoseewhetherthisisthe"unassigned"node.   If  treCategory.Nodes(lngIndex).Text=c_strUnassigned  Then  treCategory.Nodes(lngIndex).Selected=  True   '* Noneedtokeeplooking;getoutoftheloop.   ExitFor EndIf Next  lngIndex 

10.2 Use Do Loop to loop an undetermined number of times.

Sometimes you don't know the exact number of times a loop needs to be executed when the loop starts. You can start a For Next loop with an upper limit that you know is larger than the number of iterations needed (if you know that value), check for a condition within the loop, and exit the loop with an Exit For statement when the condition is met. However, this is extremely inefficient, often impractical , and just plain wrong. When you need to create a loop and you don't know how many times it needs to execute, use Do Loop .

The Do Loop construct comes in a number of flavors. In its most basic form, it has the following syntax:

  Do  [  statements  ]  Loop  

This particular loop is endless ”there is no conditional clause to determine when to stop looping. You might need an endless loop on rare occasions ”game programming comes to mind ”but more often you'll want to exit a loop when certain conditions are met.

The evaluation of a condition to terminate a Do loop can happen at the beginning or the end of the loop. Also, you can specify that a loop continue when a condition is met or until a condition is met. These options allow you a great deal of flexibility when writing loops.

The following is the syntax of a Do loop that uses the While keyword:

  DoWhile   condition  [  statements  ]  Loop  

Here's an example of a simple Do loop that uses this syntax:

  '* PopulateaTreeviewcontrolwithitemsfromthemarketing '* codestable.   DoWhileNot  (rstCodes.EOF)  Set  vntNode=treMarketing.Nodes.Add(intNodeIndex,tvwChild,,_ rstCodes![MarketingCode],"CodeImage") rstCodes.MoveNext  Loop  

As long as condition evaluates to True ”in this case, as long as the recordset's EOF (end-of-file) property is not True ”the loop continues to execute. Note that if condition evaluates to False when the loop first starts, the code between the Do and the Loop statements never executes ”not even once.

The Until keyword keeps a Do loop going until a condition is met. The following code shows the same loop rewritten using the Until keyword:

  '* PopulateaTreeviewcontrolwithitemsfromthemarketing '* codestable.   DoUntil  rstCodes.EOF  Set  vntNode=treMarketing.Nodes.Add(intNodeIndex,tvwChild,,_ rstCodes![MarketingCode],"CodeImage") rstCodes.MoveNext  Loop  

This code example works exactly like the previous one, but it offers a better solution because it eliminates the need to negate the value of rstCodes.EOF . To use the Do While loop, you have to use Not , which requires extra processing and is a little more difficult to understand.

You can use the While or Until keywords in the Loop statement (the closing statement of the loop) rather than as part of the Do statement. When you place them in the Loop statement, condition is evaluated at the end of the loop rather than at the start. This ensures that the loop always executes at least once. You must be careful when you create a loop that checks the exit condition at the end of the loop, as shown here:

  '* PopulateaTreeviewcontrolwithitemsfromthemarketing '* codestable.   Do   Set  vntNode=treMarketing.Nodes.Add(intNodeIndex,tvwChild,,_ rstCodes![MarketingCode],"CodeImage") rstCodes.MoveNext  LoopUntil  rstCodes.EOF 

Note that this code suffers from the extreme possibility of failure. It will execute at least once regardless of whether the recordset is at end-of-file. If the recordset is at end-of-file when the loop starts, an attempt to reference the MarketingCode field causes a run-time error.

As you design a loop, consider whether it needs to execute at least once. If it does, evaluate condition at the end of the loop.

As with the For Next loop, you can exit a Do loop without using GoTo and a label. To exit a Do loop at any time, use Exit Do . The following procedure creates a loop that would be endless without the Exit Do statement. In each pass through the loop, the program looks in the list box for the string "ContactNumber" . If the string is found, it is removed from the list box. When no more occurrences of the string are found, the loop is exited. Note that using Exit Do is often inferior to placing an effective exit condition at the beginning or the end of the loop.

  '* Thevalue"ContactNumber"mightappearmultipletimesinthelist '* box.Usealooptoremovealloccurrencesofthisvalue. '* SelectListItemusestheAPItoperformaquicksearch.   Do  lngIndex=SelectListItem(lstAvailableFields,"ContactNumber")  '* Checktoseewhetherthevalue"ContactNumber"wasfound.   If  lngIndex>-1  Then   '* Thevaluewasfound;removeitfromthelist.  lstAvailableFields.RemoveItemlngIndex  Else   '* Itemnotfound.Alloccurrenceshavebeenremoved,so '* exittheloop.   ExitDo EndIf Loop  

Generally, the expression that determines when to exit a loop uses a variable that's modified somewhere within the Do loop. Obviously, if the expression never changes, the loop is never exited.

Practical Applications

10.2.1 Evaluate the exit condition of a Do Loop at the start of the loop unless you have a reason to do otherwise . Often you can choose whether to place the exit condition at the beginning or at the end of a loop. However, loops whose exit conditions are evaluated at the beginning are a bit easier to understand, and they will not be executed even once if the condition is False.

Incorrect:

  IfNot  (rstReports.EOF)  Then   '* LoopthroughtheRecordsetofreports,addingeachreporttothe '* ListViewcontrol.   Do   Set  objItem=lvwReports.ListItems.Add() objItem.Text=rstReports![Title] objItem.SmallIcon="Report" rstReports.MoveNext  LoopUntil  rstReports.EOF  EndIf  

Correct:

  '* LoopthroughtheRecordsetofreports,addingeachreporttothe '* ListViewcontrol.   DoUntil  rstReports.EOF  Set  objItem=lvwReports.ListItems.Add() objItem.Text=rstReports![Title] objItem.SmallIcon="Report" rstReports.MoveNext  Loop  

10.2.2 When choosing between While and Until , use the one that allows the simplest exit condition. You can use either While or Until in most loops. However, depending on the situation, one of them (and not the other) will require that you use the Not operator to evaluate the opposite of an expression. You should use the value of a simple expression (rather than its opposite ) whenever possible.

Incorrect:

  PrivateSub  cboContacts_Requery()  '* Purpose:Fillthecontactscomboboxwithallcontacts '* forthecurrentaccount.   OnErrorGoTo  PROC_ERR  Dim  strSQL  AsString   Dim  rstContact  As  Recordset  '* Retrievethenameofallcontactsfortheaccount.  strSQL="SELECT[ContactName]FROMtblContacts"&_ "WHERE[AccountNumber]="&m_lngAccountNumber&""&_ "ORDERBY[ContactName];" cboContacts.Clear SetrstContact=dbSales.OpenRecordset(strSQL,dbOpenForwardOnly)  '* Populatethecombobox.   DoWhileNot  (rstContact.EOF) cboContacts.AddItemrstContact![ContactName] rstContact.MoveNext  Loop  PROC_EXIT:  '* ChecktoseewhetherrstContactissettoaninstance '* ofaRecordset.   IfNot  (rstContact  Is   Nothing  )  Then  rstContact.Close  Set  rstContact=  Nothing EndIf   ExitSub  PROC_ERR:  Call  ShowError(Me.Name,"cboContacts_Requery",Err.Number,_ Err.Description)  ResumeNext   EndSub  

Correct:

  PrivateSub  cboContacts_Requery()  '* Purpose:Fillthecontactscomboboxwithallcontacts '* forthecurrentaccount.   OnErrorGoTo  PROC_ERR  Dim  strSQL  AsString   Dim  rstContact  As  Recordset  '* Retrievethenameofallcontactsfortheaccount.  strSQL="SELECT[ContactName]FROMtblContacts"&_ "WHERE[AccountNumber]="&m_lngAccountNumber&""&_ "ORDERBY[ContactName];" cboContacts.Clear SetrstContact=dbSales.OpenRecordset(strSQL,dbOpenForwardOnly)  '* Populatethecombobox.   DoUntil  rstContact.EOF cboContacts.AddItemrstContact![ContactName] rstContact.MoveNext  Loop  PROC_EXIT:  '* ChecktoseewhetherrstContactissettoaninstance '* ofaRecordset.   IfNot  (rstContact  Is   Nothing  )  Then  rstContact.Close  Set  rstContact=  Nothing EndIf   ExitSub  PROC_ERR:  Call  ShowError(Me.Name,"cboContacts_Requery",Err.Number,_ Err.Description)  ResumeNext   EndSub  

10.2.3 Use a Do loop or a For Next loop instead of GoTo and a label whenever possible. In many situations, it's tempting to create a loop by using GoTo and a label. But this technique creates code that is difficult to understand and often less efficient.

Incorrect:

  PrivateFunction  StripDoubleQuotes(  ByVal  strString  AsString  )_  AsString   '* Purpose:Locatealldoublequoteswithinastringandchange '* themtosinglequotes. '* Accepts:strString-thestringinwhichtosearchfor '* doublequotes. '* Returns:ThestringpassedhereasstrString,withdouble '* quotesreplacedbysinglequotes.   Const  c_DoubleQuote=""""  Const  c_SingleQuote="'"  Dim  intLocation  AsInteger  StartCheck:  '* Lookforadoublequote.  intLocation=InStr(strString,c_DoubleQuote)  '* Ifadoublequoteisfound,replaceitwithasinglequote.   If  intLocation>0  Then   '* Adoublequotehasbeenfound.Replaceitwith '* asinglequote.  Mid$(strString,intLocation,1)=c_SingleQuote  '* Lookforanotherdoublequote.   GoTo  StartCheck  Else   '* Nomoredoublequoteswerefound.Returnthenewstring.  StripDoubleQuotes=strString  EndIf EndFunction  

Correct:

  PrivateFunction  StripDoubleQuotes(  ByVal  strString  AsString  )_  AsString   '* Purpose:Locatealldoublequoteswithinastringandchange '* themtosinglequotes. '* Accepts:strString-thestringinwhichtosearchfor '* doublequotes. '* Returns:ThestringpassedhereasstrString,withdouble '* quotesreplacedbysinglequotes.   Const  c_DoubleQuote=""""  Const  c_SingleQuote="'"  Dim  intLocation  AsInteger Do   '* Lookforadoublequote.  intLocation=InStr(strString,c_DoubleQuote)  '* Ifadoublequoteisfound,replaceitwithasinglequote.   If  intLocation>0  Then   '* Adoublequotehasbeenfound.Replaceitwith '* asinglequote.  Mid$(strString,intLocation,1)=c_SingleQuote  EndIf LoopWhile  intLocation>0  '* Returntheresult.  StripDoubleQuotes=strString  EndFunction  

10.3 Use Do Loop in place of While Wend .

The While Wend loop has been around for a long time, and its retirement is long overdue. Do Loop is more flexible, so you should use it instead. (Also, Wend just sounds ridiculous.) The While Wend loop looks like this:

  While   condition  [  statements  ]  Wend  

The While Wend loop behaves exactly like the following Do loop:

  DoWhile   condition  [  statements  ]  Loop  

There isn't much more to say about the While Wend loop. The exit condition is evaluated when the loop is started, and if the condition evaluates to True the code within the While Wend loop is executed. When the Wend statement (Middle English, anyone ?) is reached, execution jumps back to the While statement and the exit condition is reevaluated.

10.4 Use For Each Next to loop through all members of a collection.

A For Each Next loop is a powerful loop that programmers often overlook. It loops through all the members of an array or a collection of objects. Many programmers who do use For Each Next for collections don't realize that it works on arrays as well ”this is fortunate, because using For Each Next on arrays causes a noticeable decrease in performance.

For Each Next is designed and optimized to loop through collections of objects. You create an element variable, whose data type must be Variant, Object, or some specific object type, and then initiate the loop. During each pass through the loop, your element variable is set to reference an element in the collection. Manipulating the element variable directly manipulates the element in the collection. Using a For Each Next loop is often faster than using a For Next loop that utilizes the indexes of the items in a collection.

The following is the syntax for a For Each Next loop:

  ForEach   element   In   group  [  statements  ] [  ExitFor  ] [  statements  ]  Next  [  element  ] 

The element variable, which you define, must have a Variant or Object (generic or specific) data type. You should use the specific object type that is most suitable for all objects in the group (collection). For instance, when you loop through the Controls collection on a form, you can use Variant, Object, or Control as the data type. However, you can't necessarily use the TextBox, ListBox, or ComboBox data types unless you are sure that only controls of the specified type are on the form. Therefore, the logical choice is the Control data type.

Incorrect:

  PublicPropertyGet  SpecialIsFormDirty(frmForm  As  Form)  AsBoolean   '* Purpose:Determinewhetheranyofthedataontheformhas '* changed. '* Accepts:frmForm-thenameoftheformtotest. '* Returns:Trueifevenonecontrol'sDataChangedproperty '* isTrue;otherwiseFalse. '* NOTE:Errorsaretrappedbecauseattemptingtoaccess '* theDataChangedpropertyofacontrolthatdoesn't '* haveone(suchasaFrame)causesarun-timeerror.   OnErrorResumeNext   Dim  intIndex  AsInteger   Dim  blnDataChanged  AsBoolean   Const  c_NoSuchProperty=438  Const  c_NoError=0  '* Assumethattheformisdirty.  SpecialIsFormDirty=  True   '* Loopthroughallcontrolsontheform.   For  intIndex=0  To  frmForm.Controls.Count-1 Err.Clear blnDataChanged=frmForm.Controls(intIndex).DataChanged  '* Determinethetypeoferror(ifany)thatwasgenerated.   SelectCase  Err.Number  CaseIs  =c_NoError  '* ThiscontrolhasaDataChangedproperty.   If  blnDataChanged  ThenGoTo  PROC_EXIT  CaseIs  =c_NoSuchProperty  '* ThiscontroldoesnothaveaDataChangedproperty.   CaseElse   '* Legitimateerror.Sendtoerrorhandler.   GoTo  PROC_ERR  EndSelect   Next  intIndex SpecialIsFormDirty=  False  PROC_EXIT:  ExitProperty  PROC_ERR:  Call  ShowError("clsApplication","Get_IsFormDirty",Err.Number,_ Err.Description)  GoTo  PROC_EXIT  EndProperty  

Correct:

  PublicPropertyGet  SpecialIsFormDirty(frmForm  As  Form)  AsBoolean   '* Purpose:Determinewhetheranyofthedataontheformhas '* changed. '* Accepts:frmForm-thenameoftheformtotest. '* Returns:Trueifevenonecontrol'sDataChangedproperty '* isTrue;otherwiseFalse. '* NOTE:Errorsaretrappedbecauseattemptingtoaccess '* theDataChangedpropertyofacontrolthatdoesn't '* haveone(suchasaFrame)causesarun-timeerror.   OnErrorResumeNext   Dim  ctl  As  Control  Dim  blnDataChanged  AsBoolean   Const  c_NoSuchProperty=438  Const  c_NoError=0  '* Assumethattheformisdirty.  SpecialIsFormDirty=  True   '* Loopthroughallcontrolsontheform.   ForEach  ctl  In  frmForm.Controls  '* Resettheerrorobject.  Err.Clear blnDataChanged=ctl.DataChanged  '* Determinethetypeoferror(ifany)thatwasgenerated.   SelectCase  Err.Number  CaseIs  =c_NoError  '* ThiscontrolhasaDataChangedproperty.   If  blnDataChanged  ThenGoTo  PROC_EXIT  CaseIs=  c_NoSuchProperty  '* ThiscontroldoesnothaveaDataChangedproperty.   CaseElse   '* Legitimateerror.Sendtoerrorhandler.   GoTo  PROC_ERR  EndSelect   Next  ctl SpecialIsFormDirty=  False  PROC_EXIT:  ExitProperty  PROC_ERR:  Call  ShowError("clsApplication","Get_IsFormDirty",Err.Number,_ Err.Description)  GoTo  PROC_EXIT  EndProperty  

Practical Applications

10.4.1 Don't use For Each Next to loop through arrays. When you use a For Each Next loop to manipulate the elements of an array, your element variable must have type Variant. If the elements of the array are not Variants, Visual Basic must coerce them to type Variant to use For Each . The result is much slower code.

Incorrect:

  Dim  lngResult  AsLong Dim  vntUndoBitMap  AsVariant   '* PopulateallUndomemorybitmapswiththecurrentimage.   ForEach  vntUndoBitMap  In  m_alngUndo()  With  vntUndoBitMap .UndoDC=CreateCompatibleDC(frmEditor.picPreview.hdc) .UndoHandle=CreateCompatibleBitmap(Me.picPreview.hdc,_ c_ImageWidth,c_ImageHeight) lngResult=SelectObject(.UndoDC,.UndoHandle)  EndWith Next  vntUndoBitMap 

Correct:

  Dim  lngResult  AsLong Dim  intCounter  AsInteger   '* PopulateallUndomemorybitmapswiththecurrentimage.   For  intCounter=c_MinUndo  To  c_MaxUndo  With  m_alngUndo(intCounter) .UndoDC=CreateCompatibleDC(frmEditor.picPreview.hdc) .UndoHandle=CreateCompatibleBitmap(Me.picPreview.hdc,_ c_ImageWidth,c_ImageHeight) lngResult=SelectObject(.UndoDC,.UndoHandle)  EndWith Next  intCounter 

10.4.2 Use the most specific data type possible in a For Each Next loop. The element variable in a For Each Next loop must be a Variant or some type of Object (generic or specific) variable. Variants have many drawbacks (as discussed in Chapter 6, "Variables"). If you use a Variant or generic Object when a specific Object type would work, you prevent Visual Basic from determining errors at compile time (such as referencing properties that don't exist for that type).

Incorrect:

  Dim  vntControl  AsVariant   '* Forcearefreshofalltextboxesontheform.   ForEach  vntControl  In  Me.Controls  '* Checktoseewhetherthiscontrolisatextbox.   If  TypeName(vntControl)="TextBox"  Then  vntControl.Refresh  EndIf Next  vntControl 

Also incorrect:

  Dim  objControl  AsObject   '* Forcearefreshofalltextboxesontheform.   ForEach  objControl  In  Me.Controls  '* Checktoseewhetherthiscontrolisatextbox.   If  TypeName(objControl)="TextBox"  Then  objControl.Refresh  EndIf Next  objControl 

Correct:

  Dim  ctl  As  Control  '* Forcearefreshofalltextboxesontheform.   ForEach  ctl  In  Me.Controls  '* Checktoseewhetherthiscontrolisatextbox.   If  TypeName(ctl)="TextBox"  Then  ctl.Refresh  EndIf Next  ctl 


Practical Standards for Microsoft Visual Basic
Practical Standards for Microsoft Visual Basic .NET (Pro-Developer)
ISBN: 0735613567
EAN: 2147483647
Year: 2000
Pages: 57

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net