こんにちは!VBAの沼(そしてその先の絶景)へようこそ。チーフアーキテクトの私です。
マクロの記録から一歩踏み出し、「自分だけの自動化ツールを作りたい!」と思ったときに出会うのが、この「コレクション(Collection)」という強力なデータ構造ですよね。複数のデータをまとめて管理できるため、業務効率化の心強い味方です。
しかし、多くの人がここで最初の大きな壁にぶつかります。
「コレクションの要素をループで回しながら、不要なものを削除しようとすると、なぜかエラーが出る、あるいはデータがすり抜けてしまう……!」
今回は、この現象の正体と、プロの現場では常識となっている「安全な削除の鉄則」を、優しく、そして深く解説していきます。ここをクリアすれば、あなたのVBAスキルは確実にワンランク上のステージに上がりますよ。
—
1. なぜエラーが起きるのか?「インデックスのズレ」という罠
まずは、何が起きているのかを視覚的にイメージしてみましょう。
ここに、4つの要素が入ったコレクションがあるとします。
[初期状態]
インデックス(番地): 1 2 3 4
要素の値: “A” “B” “C” “D”
あなたが「前から順番に(`For i = 1 To 4` で)要素をチェックして、”B” を削除したい!」と思ったとします。
1. `i = 1`:「”A”だね。OK」
2. `i = 2`:「”B”だね。削除しよう!」 -> ここで事件が起きます。
“B”(2番地)が消えた瞬間、VBAは後ろにあった要素を自動的にグイッと前へ詰めます。
[“B”を削除した瞬間]
インデックス(番地): 1 2 3
要素の値: “A” “C” “D” (あれ?さっきの4番地の”D”が3番に繰り上がったぞ…)
3. `i = 3`:「次は3番地をチェックっと……あれ?”C”を飛ばして、いきなり”D”になっちゃったぞ!?」
これが、ループ中に要素を削除するときに発生する「インデックスのズレ(とそれに伴うデータスキップ、あるいは範囲外エラー)」の正体です。コレクションや配列を前から順に削除していくと、必ずこの罠にハマります。
—
2. 解決策の核心:「後ろから前へ」ループを回す
では、どうすればこのズレを防げるのでしょうか?
答えは拍子抜けするほどシンプルです。「後ろから前へ逆向きにループを回す」のです。
先ほどの例で考えてみましょう。4番地からスタートして、3番地、2番地、1番地へとカウントダウンしていくイメージです。
- `i = 4` (“D”) を削除しても、影響を受けるのは「4番地より後ろ」の要素だけ。これからチェックする「3番地より前」のインデックスは一切ズレません!
これこそが、アーキテクトたちが愛用する最も安全で美しいアプローチです。
—
3. 実践!安全に要素を削除するVBAコード
百聞は一見にしかず。実際に動かせるサンプルコードを見てみましょう。
ここでは、コレクションの中から「”りんご”」という文字が含まれる要素だけを綺麗に根こそぎ削除するマクロを書いてみます。
Sub RemoveItemsSafely()
‘ 1. コレクション変数を宣言する
Dim fruitCollection As Collection
Set fruitCollection = New Collection
‘ 2. テストデータを追加する
fruitCollection.Add “みかん”
fruitCollection.Add “りんご”
fruitCollection.Add “バナナ”
fruitCollection.Add “りんご”
fruitCollection.Add “ぶどう”
MsgBox “削除前の要素数: ” & fruitCollection.Count, vbInformation, “確認”
‘ ==========================================================
‘ 【重要】コレクションから要素を削除する鉄則:後ろから前にループする!
‘ ==========================================================
Dim i As Long
For i = fruitCollection.Count To 1 Step -1
‘ 後ろ(Count番目)から 1番目に向かって「Step -1」でカウントダウン
If fruitCollection(i) = “りんご” Then
‘ “りんご”を見つけたら削除!
‘ 前の要素のインデックスは影響を受けないのでエラーが起きません。
fruitCollection.Remove i
End If
Next i
‘ 3. 結果の確認
Dim resultMsg As String
resultMsg = “削除後の要素数: ” & fruitCollection.Count & vbCrLf & _
“(すべての「りんご」が安全に削除されました)”
MsgBox resultMsg, vbInformation, “処理完了”
End Sub
コードのポイント解説
- `For i = fruitCollection.Count To 1 Step -1`
これが最大のキモです。`Step -1` を指定することで、カウンターを逆向きに回しています。
- `fruitCollection.Remove i`
インデックス番号を指定して要素を削除しています。逆向きループのおかげで、削除しても処理中のインデックスが狂うことがありません。
—
4. もう一つのアプローチ:エラーを回避する「条件の反転」
もし何らかの理由で「どうしても前から順番にループを回したい!」という場合は、削除するのではなく、「残したいものだけを新しいコレクションに集め直す」という設計思想(別名:フィルタリング)をとるのがプロの定石です。
Sub FilterCollectionInsteadOfRemove()
Dim original As Collection, filtered As Collection
Set original = New Collection
Set filtered = New Collection
‘ データ追加(省略)
original.Add “みかん”
original.Add “りんご”
original.Add “バナナ”
Dim item As Variant
For Each item In original
‘ 「りんご以外」であれば、新しいコレクションに「採用」する
If item <> “りんご” Then
filtered.Add item
End If
Next item
‘ 差し替え
Set original = filtered
MsgBox “「りんご」を除外した新しいコレクションができました!”
End Sub
この方法であれば、`For Each` 構文が使えるためコードが非常に直感的になり、インデックスの管理すら不要になります。メモリの消費量やパフォーマンスが問題にならない規模であれば、実はこちらの方が安全でバグを生みにくいケースも多いです。
—
まとめ:ここをクリアすればVBAの基本はバッチリ!
今回は、コレクションの反復処理における最大の落とし穴と、そのスマートな回避策について解説しました。
1. 前からループを回して `Remove` すると、インデックスがズレて破綻する。
2. 対策①:`Step -1` を使って「後ろから前へ」逆順ループを回す。
3. 対策②:`For Each` で「残したいものだけを別箱に移す(フィルタリング)」発想を持つ。
この2つの引き出しを持っていれば、今後どれだけ複雑なデータを扱うマクロを書くことになっても、データ構造のバグで頭を抱えることはもうありません。
日々の業務自動化を、もっとエレガントに、もっとロバスト(堅牢)に。
あなたのVBAライフが素晴らしいものになることを、エンジニアとして心から応援しています!
