Collection は For Each 中でも要素を足してみよう 

## Collection と　For Each
VBA では、要素数が未知の場合にまず`VBA.Collection`を使いますね。
そんな`VBA.Collection`ですが、先頭要素からの線形リストになっているらしく、以降の要素にもアクセスする場合はひとつひとつ添え字の指定をするのは非効率なので`For Each`で順次アクセスするのが一般的です。

ただし`VBA.Collection`には、C#の`yield`のように要素を1つ求めた時点で取得処理に処理を戻すような機構がありません。
そのため、要素を一括で作成してから`For Each`を開始するような書き方をよくします。

```vb:AfterAllAdd
    Dim TestColl As New VBA.Collection
    
    Dim i As Long
    For i = 1 To 5
        Call TestColl.Add("Value" & Format(i, "00"))
    Next i
    
    Dim vItem As Variant
    For Each vItem In TestColl
        Debug.Print vItem
    Next vItem
```
この場合、例えば要素が100個のものに対して使うのが30個までだった場合などに、残りの70個の作成処理が無駄になってしまいます。

## Collection の末尾の判定
そんな`VBA.Collection`ですが、`For Each`の中でループを継続するかどうかの判定は毎回行っているため、`For Each`の途中で要素を追加・削除することができます。

``` vb:CollectionAddInLoop
    Dim TestColl As New VBA.Collection
    
    Call TestColl.Add("A")
    Call TestColl.Add("B")
    
    Dim vItem As Variant
    For Each vItem In TestColl
        Debug.Print vItem
        Select Case vItem
        Case "A"
            Call TestColl.Add("C")
            Call TestColl.Add("D")
        Case "C"
            Call TestColl.Add("E")
        End Select
    Next vItem
```
実行結果

```CollectionAddInLoop_Result
A
B
C
D
E
```

ただし、末尾の要素までたどりついてしまうとそこから末尾に足してもループの継続は行われなくなります。

``` vb:CollectionAddOnTail
    Dim TestColl As New VBA.Collection
    
    Call TestColl.Add("A")
    Call TestColl.Add("B")
    
    Dim vItem As Variant
    For Each vItem In TestColl
        Debug.Print vItem
        Select Case vItem
        Case "A"
            Call TestColl.Add("C")
        Case "C"
            Call TestColl.Add("D")
        End Select
    Next vItem
```
実行結果

```CollectionAddInLoop_Result
A
B
C
```

##ジョブキューとしての Collection
ループの中で要素を追加していく例としてジョブキューを考えてみます。

```vb:JobQueue_Try
Private Enum JobType
    Job_A
    Job_B
    Job_C
End Enum

Sub Main()
    Dim JobQueue As New Collection
    Call JobQueue.Add(Job_B)
    Call JobQueue.Add(Job_C)
    
    Dim CurJob As Variant 'As JobType
    For Each CurJob In JobQueue
        Select Case CurJob
        Case Job_A
            Call JobA(JobQueue)
        Case Job_B
            Call JobB(JobQueue)
        Case Job_C
            Call JobC(JobQueue)
        End Select
        Call JobQueue.Remove(1)
    Next CurJob
End Sub

Private Sub JobA(ByVal JobQueue As Collection)
    Debug.Print "Job.A"
    Call JobQueue.Add(Job_C)
End Sub

Private Sub JobB(ByVal JobQueue As Collection)
    Debug.Print "Job.B"
    Call JobQueue.Add(Job_C)
    Call JobQueue.Add(Job_A)
End Sub

Private Sub JobC(ByVal JobQueue As Collection)
    Debug.Print "Job.C"
End Sub
```
実行結果

```JobQueue_Try_Result
Job.B
Job.C
Job.C
Job.A
```
Job.A の後には Job.C が続いてほしいのですが、Job.A に差し掛かった時点で `For Each`が末尾に到達したために、続きの Job.C が足されたことが認識されませんね。

こういう場合は、要素を追加したときにダミーでさらにもう1要素足しておくような使い方がいいのかもしれません。

``` vb:JobQueueWithDummy
Private Enum JobType
    NOP
    Job_A
    Job_B
    Job_C
End Enum

Sub Main()
    Dim JobQueue As New Collection
    Call JobQueue.Add(Job_B)
    Call JobQueue.Add(Job_C)
    
    Dim CurJob As Variant 'As JobType
    For Each CurJob In JobQueue
        Select Case CurJob
        Case NOP
            '
        Case Job_A
            Call JobA(JobQueue)
        Case Job_B
            Call JobB(JobQueue)
        Case Job_C
            Call JobC(JobQueue)
        End Select
        Call JobQueue.Remove(1)
    Next CurJob
End Sub

Private Sub JobA(ByVal JobQueue As Collection)
    Debug.Print "Job.A"
    Call JobQueue.Add(Job_C)
    Call JobQueue.Add(NOP)
End Sub

Private Sub JobB(ByVal JobQueue As Collection)
    Debug.Print "Job.B"
    Call JobQueue.Add(Job_C)
    Call JobQueue.Add(Job_A)
    Call JobQueue.Add(NOP)
End Sub

Private Sub JobC(ByVal JobQueue As Collection)
    Debug.Print "Job.C"
End Sub
```
実行結果

```JobQueueWithDummy_Result
Job.B
Job.C
Job.C
Job.A
Job.C
```
ジョブキューの場合は途中にダミー要素があってもほとんど影響ありませんが、作られた`Collection`の要素一式に意味があるものの場合は、その内容に応じて末尾の要素での追加を避けるしくみを考える必要がありますね。