こんにちは!いつも開発お疲れ様です。あなたの頼れる先輩エンジニアです。
Excelのマクロ(VBA)を書いていて、「もっと本格的なシステムを作りたい!」「処理速度を劇的に速くしたい!」と一念発起してVB.NETの世界に飛び込んできた方、あるいはプログラミングの基礎としてVB.NETを学び始めた方、ようこそ!
VB.NETは、VBAの手軽さを残しつつ、モダンで超強力なシステムを構築できる素晴らしい言語です。
しかし、VBAの感覚のままVB.NETのコードを書いていると、知らず知らずのうちに「深刻なパフォーマンスの罠」にハマってしまうことがあります。その代表例が、今回解説する「配列の動的リサイズ(`ReDim Preserve`)」です。
「ここをクリアすれば、Visual Basic (VB / VB.NET)の基本はバッチリですよ!」と言える極めて重要なポイントを、メモリの裏側の動きまで含めて優しく、そしてディープに解説します。一緒にステップアップしていきましょう!
—
1. 配列の基本:固定長配列と動的配列の「初期化」
まずは基本のおさらいから始めましょう。配列とは、「同じ種類のデータを一列に並べて管理するための仕切り付きの箱」です。
VB.NETにおける配列の作り方(初期化)には、大きく分けて「固定長配列」と「動的配列」の2種類があります。
固定長配列(サイズが決まっている箱)
プログラムを書く時点で、中に入れるデータの個数が決まっている場合に使います。
‘ 要素数5個(インデックスは 0 から 4 まで)の整数型配列を宣言
Dim scores(4) As Integer
‘ 初期値を直接指定して初期化することもできます
Dim伝言板() As String = {“おはよう”, “こんにちは”, “おやすみ”}
動的配列(後からサイズを決める箱)
プログラムを実行するまで、データの個数が分からない場合に使います。最初はサイズを指定せずに宣言し、後から `ReDim` キーワードでサイズを決定します。
‘ 最初は中身が空っぽ(Nothing)の状態で宣言
Dim customerNames() As String
‘ 処理の途中で、要素数を 10(インデックス 0〜9)に設定
ReDim customerNames(9)
「なんだ、後からサイズを自由に変えられるなら、全部動的配列にすれば便利じゃないか!」と思いますよね。
しかし、ここに初心者キラーの罠が潜んでいるのです。
—
2. 知っておきたい `ReDim Preserve` の裏側で起きている「メモリの悲劇」
動的配列のサイズを「それまでのデータを消さずに」変更したいとき、VB/VBAではおなじみの `ReDim Preserve` を使います。
‘ 要素数を 10 から 11 に増やし、かつ中身を維持する
ReDim Preserve customerNames(10)
一見、スマートに箱が1つ増えたように見えますが、コンピュータのメモリの中(ヒープ領域)では、恐ろしいほどの重労働が行われています。
メモリ空間で起きている「引っ越し」の真実
メモリの上では、配列は「連続した一列の隙間のない土地」に確保されます。そのため、後ろにポンッと空き地を足すような器用な真似はできません。
`ReDim Preserve` が実行されると、システムは以下のステップをすべて手作業で行います。
【ReDim Preserveの内部動作イメージ】
1. 新しい場所(メモリ)に、一回り大きい「新しい配列」を新規に作成する。
[ 新しい配列 ] [ ][ ][ ][ ] (新規確保)
2. 古い配列の中身を、新しい配列へ1つずつ丁寧に「コピー(複製)」する。
[ 古い配列 ] ── データをコピー ──> [ 新しい配列 ]
3. コピーが終わったら、古い配列を「ゴミ(破棄対象)」として捨てる。
[ 古い配列 ] (ゴミ箱へ ── 後でGCが回収)
要素数が数個なら一瞬ですが、これが数千、数万回のループの中で毎回行われたらどうなるでしょうか?
プログラムは「新しい配列の確保」「データのコピー」「古い配列の破棄(ガベージコレクションの負荷)」を延々と繰り返し、CPUとメモリは悲鳴を上げ、動作はカタツムリのように遅くなってしまいます。
—
3. 実感してみよう!`ReDim Preserve` の限界を示す実験コード
どれだけパフォーマンスに差が出るか、簡単な実験コードで確かめてみましょう。
「1万回、データを末尾に追加していく処理」を、`ReDim Preserve` を使って書いてみます。
Imports System.Diagnostics
Module Module1
Sub Main()
Dim stopwatch As New Stopwatch()
‘ — 悪夢の ReDim Preserve パターン —
Console.WriteLine(“処理を開始します。少しお待ちください…”)
stopwatch.Start()
Dim dynamicArray() As Integer = {} ‘ 空の配列
For i As Integer = 1 To 50000
‘ 毎回サイズを1つ増やしてデータを維持する
ReDim Preserve dynamicArray(i – 1)
dynamicArray(i – 1) = i
Next
stopwatch.Stop()
Console.WriteLine($”ReDim Preserveでの処理時間: {stopwatch.ElapsedMilliseconds} ms”)
‘ ※環境によっては数秒、あるいはそれ以上かかります
End Sub
End Module
5万回のループを実行すると、パソコンのファンが回り、処理が終わるまでに明らかな「待ち時間」が発生するはずです。これは、裏側で5万回も引っ越し(メモリ再確保と全コピー)を繰り返しているからです。
—
4. 現代の救世主:`List(Of T)` への移行
このパフォーマンスの限界を突破するために、.NET Framework / .NET Core で用意された「現代の標準的な武器」が `List(Of T)`(リスト) です。
`List(Of T)` は、データの追加や削除を極めて高速に行えるように設計された、賢いコレクション(データの集まり)です。
なぜ `List(Of T)` は速いのか?
`List(Of T)` も内部的には配列を使っています。しかし、`ReDim Preserve` のように「1個増えるたびに引っ越し」はしません。
中身が足りなくなると、「現在の容量の2倍」の空き地を一気に確保するというスマートな戦略(指数関数的リサイズ)を取ります。引っ越しの回数を最小限に抑えることで、劇的な高速化を実現しているのです。
`List(Of T)` の使い方
使い方は非常にシンプルです。`(Of T)` の `T` には、格納したいデータの型(Integer や String など)を入れます。
‘ 整数(Integer)を格納するリストを生成
Dim numberList As New List(Of Integer)()
‘ データの追加は Add メソッドを呼ぶだけ!
numberList.Add(10)
numberList.Add(20)
numberList.Add(30)
‘ 途中の要素にアクセスするのも配列と同じ感覚です
Dim firstItem As Integer = numberList(0) ‘ 10 が取得できる
‘ 必要があれば、一瞬で本物の「配列」に変換することも可能です
Dim normalArray() As Integer = numberList.ToArray()
—
5. 劇的ビフォーアフター:パフォーマンス比較
それでは、先ほどの5万回の処理を `List(Of T)` に書き換えてみましょう。
Imports System.Diagnostics
Module Module2
Sub Main()
Dim stopwatch As New Stopwatch()
‘ — 快適な List(Of T) パターン —
stopwatch.Start()
Dim smartList As New List(Of Integer)()
For i As Integer = 1 To 50000
‘ リストへ要素を追加(裏で賢くメモリを管理してくれる)
smartList.Add(i)
Next
stopwatch.Stop()
Console.WriteLine($”List(Of T)での処理時間: {stopwatch.ElapsedMilliseconds} ms”)
End Sub
End Module
結果はどうなる?
- `ReDim Preserve` パターン: 数秒(環境によっては数万ミリ秒)
- `List(Of T)` パターン: わずか 1〜2 ミリ秒!(文字通り「一瞬」です)
目に見えて圧倒的な差が出ましたね。これが、アルゴリズムとメモリ管理を最適化することのパワーです。
—
6. コレクション選びの「黄金ルール」
「じゃあ、これからは全部 `List(Of T)` でいいの?」
その疑問にお答えするために、先輩から開発現場で使える「コレクション選びの判断基準」を授けます。
【データ管理クラス選定のチャート】
データの個数は最初から決まっている?
├── [ Yes ] ──> 「通常の配列(固定長配列)」を使う
│ (メモリ消費が最も少なく、アクセスが最速)
│
└── [ No ] ──> データの追加・削除が頻繁に発生する?
├── [ Yes ] ──> 「List(Of T)」を使う ★基本はこれ!
└── [ No ] ──> (初期設定後、サイズが変わらないなら)
List(Of T)で作った後、.ToArray() で配列に固定する
- 配列(Array)が適しているケース:
「1年は12ヶ月」「1週間は7日」のように、要素数が絶対に変わらないデータ。
- `List(Of T)` が適しているケース:
CSVファイルから読み込んだ行データ、ユーザーが画面で入力した件数など、実行するまで件数が読めないデータ。
—
まとめ:動的配列を卒業して、ワンランク上のエンジニアへ!
ここまでの内容を整理しましょう。
1. `ReDim Preserve` は、裏側で「新しいメモリ確保」「コピー」「古いメモリの破棄」を行うため、ループ内で多用するとシステムが極端に重くなる。
2. 現代のVB.NETでは、サイズが変わるデータの管理には `List(Of T)` を使うのが標準かつ最強の選択肢。
3. どうしても配列として他クラスにデータを渡したい時は、`List(Of T)` でデータを受け止めてから最後に `.ToArray()` で変換するのがスマート。
この知識をマスターすれば、VBAの「マクロ」の領域を完全に脱却し、.NETのパワーをフルに引き出せる「一人前のVB.NET開発者」へ大きく一歩前進です。
コードの裏側にある「メモリの気持ち」を少しだけ意識して、より軽快でユーザーに愛されるシステムを作っていきましょう。応援しています!
