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 IterationItem
s 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...
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:
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.
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.