Search code examples
excelvbams-wordvb6powerpoint

Solution for limitations of For Each loops?


When cycling through a list of items in VBA, I use this code pattern:

For Each Item In ListOfItems
    ' Do something with Item
Next Item

However, in practical applications, I often need access to an iteration counter, the previous or next item, or I have to perform special steps for the first or last item in the list. For instance:

For Each Item In ListOfItems
    If Item.IsFirst Then
        ...
    End If
    Debug.Print Item.Index, Item.SomeProperty
Next Item

However, .Index and .IsFirst are not properties of the class of Item. Therefore, this does not work. So, my question to the community is: How can I get as close as possible to my desired code pattern?

I know that I could implement a counter with some additional code. Checking for the first item could as well be performed with some additional code. But that's not what I want. I have seen many bugs where the developer forgot to increment the counter. I want to have this out of the hands of the developer.

Likewise, I also know that I could use other types of loops, such as Do While or For i=1 To .... That's also not what I want. I want to use THE straight-forward code pattern, which is made for cycling through a list, which in my opinion is For Each ....

My own attempts to solve this led to this pattern:

For Each IterationItem In toIterationItems(ListOfItems)
    If IterationItem.IsFirst Then
        ...
    End If
    Debug.Print IterationItem.Index, IterationItem.Item.SomeProperty
Next IterationItem

This works. The function toIterationItems takes a Collection or Dictionary and for each element, it puts an IterationItem into an output Collection, which is handed back to the For Each. So, For Each cycles through a Collection of IterationItems instead of the original ListOfItems.

While this works (I can post the code here, if desired), I am unsure, if there aren't any better approaches. My approach has some disadvantages...


Solution

  • Your approach has the merit of abstracting away the need for counter management, which indeed can be a source of bugs. However, as you mention, it comes with some downsides. It introduces another level of indirection and complexity, which might confuse less experienced developers or make the code harder to maintain. Additionally, it involves creating a whole new collection just to iterate over an existing one, which is inefficient especially for large collections.

    The primary alternatives in VBA would be:

    1. As you mentioned, managing the counter manually. Yes, it can lead to bugs if not done carefully, but if the loop is simple and well-documented, the risks are minimal. For complex loops, an abstraction like your IterationItem might indeed be useful.

    2. Using a different language feature or construct. You mentioned you want to use For Each, but it's worth noting that the more traditional For loop inherently supports accessing the index, the previous item, and the next item, and it also makes it easy to perform specific actions on the first or last item.

    Here's an example of how you could use a For loop to do all these things:

    Dim ListCount As Long
    ListCount = ListOfItems.Count
    
    For i = 1 To ListCount
        Dim CurrentItem As Variant
        CurrentItem = ListOfItems(i)
        
        If i = 1 Then
            ' First item
        ElseIf i = ListCount Then
            ' Last item
        Else
            ' Middle items
            Dim PreviousItem As Variant
            Dim NextItem As Variant
            PreviousItem = ListOfItems(i - 1)
            NextItem = ListOfItems(i + 1)
        End If
        
        Debug.Print i, CurrentItem.SomeProperty
    Next i
    

    If you prefer the readability of For Each and don't mind the added complexity and inefficiency, then your IterationItem approach might be the best for you. But if you want something simpler and more efficient, a For loop is the most natural fit for the requirements you described. It's just a matter of trade-offs between readability, simplicity, and efficiency.