こんにちは!VBAや古いVB6の自動化マクロから一歩進み、本格的な.NETの世界へと踏み出した皆さん、日々の開発お疲れ様です。
「Excelマクロの記録」や「なんとなく動くコード」から脱却し、Visual Basic .NET(VB.NET)を使いこなせるようになると、業務自動化の可能性は一気に広がります。
今回は、VB.NETでのプログラミングにおいて、初学者が100%と言っていいほど一度はハマる「コレクション走査の罠」について解説します。
ここをクリアすれば、エラーに怯えることなく、安全で美しいコードが書けるようになりますよ。一緒に本質をマスターしていきましょう!
—
1. コレクションをループする2つの方法(For と For Each)
VB.NETでリストや配列などの複数のデータを処理するとき、主に`For`ループと`For Each`ループを使います。まずは、それぞれの特徴と「何をしているのか」を整理しておきましょう。
① インデックスで数える:`For` ループ
「0番目から最後の番目まで、何番目という番号を指定して順番に処理する」やり方です。
.net
Dim names As New List(Of String) From {“田中”, “鈴木”, “佐藤”}
‘ 0からリストの数-1までループする
For i As Integer = 0 To names.Count – 1
Console.WriteLine(names(i))
Next
② 要素を直接取り出す:`For Each` ループ
「リストの中身を先頭から順に、一つずつ取り出して変数に入れて処理する」やり方です。コードが非常にスッキリするのが特徴です。
.net
Dim names As New List(Of String) From {“田中”, “鈴木”, “佐藤”}
‘ 1つずつ取り出して name という変数に入れる
For Each name As String In names
Console.WriteLine(name)
Next
直感的で読みやすい `For Each` は、日々のコーディングで大活躍します。「特に理由がなければ `For Each` を使おう」というのが、VB.NETの基本方針です。
—
2. 現場で頻発!「ループ中に要素を消すと死ぬ」バグの正体
さて、ここからが本題です。
例えば、「リストの中から『エラー』という文字が含まれるデータを見つけて削除したい」と思ったとします。
初学者がやってしまいがちな、やってはいけないコードがこちらです。
.net
‘ 【NGコード】For Eachで回しながら要素を削除する
Dim tasks As New List(Of String) From {“正常”, “エラー”, “正常”, “エラー”}
For Each task As String In tasks
If task = “エラー” Then
tasks.Remove(task) ‘ ← ここで爆発(例外発生)します!
End If
Next
このコードを実行すると、`InvalidOperationException`(無効な操作例外)というエラーが発生し、プログラムが強制終了します。
なぜエラーになるのか?(舞台裏の仕組み)
`For Each` ループは、裏側で「イテレータ(反復子)」という仕組みを使ってコレクションを監視しています。
これは言わば、「今、リストの何番目を読んでいるか」を厳密に管理している保安官のようなものです。
保安官は、「よし、順番通りに綺麗に並んでいるな」と確認しながら進んでいます。
そこに、ループの途中で `tasks.Remove(task)` が実行されるとどうなるでしょう?
リストから勝手に要素が消え、後ろのデータが前に詰まってしまいます。
保安官からすると、「あれっ!?数えていた人数が勝手に変わったぞ!全体の秩序が乱れた!」となり、パニックを起こして例外を投げてプログラムを止めてしまうのです。これが、コレクション走査中の削除バグの正体です。
—
3. 安全に要素を削除・変更するための正しいアプローチ
では、ループ処理の途中で条件に合う要素を削除したい場合は、どう書けばよいのでしょうか?
実務で使える安全な2つのアプローチを伝授します。
アプローチA:`For` ループを「後ろから」回す(王道テクニック)
インデックスを使う `For` ループであれば、後ろの番号(末尾)から先頭に向かってカウントダウンすることで、削除してもインデックスが狂わなくなります。
.net
‘ 【OKコード】Forループで後ろから逆順に処理する
Dim tasks As New List(Of String) From {“正常”, “エラー”, “正常”, “エラー”}
‘ Count – 1 から 0 まで、ステップを -1 しながらループ
For i As Integer = tasks.Count – 1 To 0 Step -1
If tasks(i) = “エラー” Then
tasks.RemoveAt(i) ‘ 後ろから消していくので、前のインデックスに影響しない!
End If
Next
★ここがポイント:
なぜ後ろからだと安全なのか?
例えば、要素が3つあって、一番後ろの「2番目」を消しても、「0番目」や「1番目」のインデックス番号は変わりませんよね。そのため、保安官をパニックにさせずに安全に処理できるのです。
—
アプローチB:LINQの `RemoveAll` を使う(現代的でスマートな方法)
.NETの強力な機能であるLINQ(ラムダ式)を使うと、ループすら書かずに一撃で安全に要素を削除できます。プロっぽいスマートなコードです。
.net
‘ 【超スマートコード】RemoveAllを使う
Dim tasks As New List(Of String) From {“正常”, “エラー”, “正常”, “エラー”}
‘ 「”エラー” と一致するものをすべて削除する」という条件を渡す
tasks.RemoveAll(Function(t) t = “エラー”)
一行で記述でき、内部で最も効率的かつ安全にメモリを操作してくれるため、現代のVB.NET開発では非常に好まれる書き方です。
—
まとめ:ここをクリアすれば基本はバッチリ!
今回は、VB.NETのループ処理とコレクションの安全な操作について解説しました。
- `For Each` は読みやすいが、ループ中に要素の追加・削除をしてはいけない。
- 要素を削除したいときは、「後ろから `For` ループ」か「`RemoveAll`」を使う。
この原則を知っているだけで、現場でよくある「原因不明の例外エラー」の大部分を防ぐことができます。
「動けばいいや」のコードから、「仕組みを理解した堅牢なコード」へ。
一歩ずつ、確実にスキルアップしていきましょう。ここをクリアしたあなたなら、もう初級者卒業は目の前です!
