【テクニカル・上級編】初心者向け:VB.NETでの配列の初期化とReDim Preserveのパフォーマンス最適化:動的配列の罠とList(Of T)への移行 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

魂のアーキテクチャ設計:VB.NET配列の呪縛と、`ReDim Preserve` が引き起こすメモリの悲劇

レガシーなVB6(Visual Basic 6.0)の遺産を抱えたまま、あるいはその呪縛を引きずったままVB.NETの世界に足を踏み入れる開発者が後を絶たない。
「とりあえず配列を作って、足りなくなったら `ReDim Preserve` で広げればいい」
もし君が、あるいは君のチームのジュニアエンジニアがそう考えているならば、今すぐその手を止めさせたまえ。

プログラミング言語の進化は、単なるシンタックスの糖衣(シュガー)ではない。それはメモリ管理モデルのパラダイムシフトなのだ。今回は、VB.NETにおける動的配列のメカニズム、`ReDim Preserve` が秘める致命的なパフォーマンスの罠、そして真にモダンなシステム構築のために我々が選ぶべき `List(Of T)` への移行戦略について、アーキテクトの視点から一切の妥協なく解説する。

1. 配列の基礎:固定長配列と動的配列のメモリ構造

まず、物理メモリ上で配列がどのように振る舞うかを理解しなければならない。
配列の本質は、「連続したメモリ領域の確保」である。

.net
‘ 固定長配列の宣言
Dim fixedArray(9) As Integer ‘ 10個のInteger型要素(合計40バイト)を連続領域に確保

固定長配列は、宣言された瞬間に必要なサイズがヒープ(またはスタック)上に確保される。インデックスアクセスが $O(1)$ という圧倒的な高速性を誇るのは、メモリーの先頭アドレスから「要素サイズ × インデックス」という単純なオフセット計算だけで物理アドレスを特定できるからに他ならない。

問題は、サイズが事前に確定しないデータを扱う場合の動態である。

`ReDim Preserve` の背後にある「残酷な現実」

要素数が不明なデータを扱うため、VB.NET(およびVB6の血統)には `ReDim` が用意されている。そして既存のデータを保持したままサイズを変更する `ReDim Preserve` は、一見すると魔法の杖のように見える。

だが、チーフアーキテクトとして断言しよう。`ReDim Preserve` は、コードの中で最も乱用されてはならない破壊的オペレーションの一つである。

裏側で何が起きているか?
連続したメモリ領域の背後には、常に「隣の領域が空いているとは限らない」という物理的な制約が存在する。

1. 現在確保されているメモリの末尾に、拡張分の連続領域を確保できる余地がない。
2. 仕方なく、より大きな新しいメモリ領域を別の場所に新しく確保する。
3. 既存の全要素を、古い領域から新しい領域へ丸ごとコピーする。
4. 古いメモリ領域を破棄する。

お分かりだろうか?
要素数を $N$ から $N+1$ へ増やすたびに、$O(N)$ のメモリコピーが発生する。これをループ内で毎回実行したとき、計算量は $O(N^2)$(二乗オーダー) に跳ね上がる。数万件のデータを読み込むバッチ処理でこれをやれば、CPUは瞬く間に飽和し、GC(ガベージコレクター)のヒープ領域は断片化(メモリフラグメンテーション)の泥沼に沈む。

2. 【実証】`ReDim Preserve` が引き起こすパフォーマンスの劣化

百聞は一見にしかず。以下のコードを見てほしい。これは、レガシーな発想で書かれた「やってはいけない」典型例である。

.net
Imports System.Diagnostics

Module ArrayPerformanceTrap
Sub Main()
Const iterations As Integer = 50000
Dim data() As Integer = New Integer(-1) {}

Dim sw As Stopwatch = Stopwatch.StartNew()

‘ 毎回 ReDim Preserve を呼ぶ地獄のループ
For i As Integer = 0 To iterations – 1
ReDim Preserve data(i)
data(i) = i
Next

sw.Stop()
Console.WriteLine($”ReDim Preserve 実行時間: {sw.ElapsedMilliseconds} ms”)
End Sub
End Module

このコードをリリースビルドで走らせてみるとよい。たった5万件のインクリメントごときに、驚くほどの時間がかかることが体感できるはずだ。もしこれが50万件、100万件となれば、プログラムはフリーズしたかのような挙動を示す。

3. 救世主 `List(Of T)`:内部実装の智慧

では、我々はこの呪縛からどう逃れればいいのか? 答えは `.NET Framework 2.0` 以降、ジェネリクスと共に我々に手渡されている。
そう、`System.Collections.Generic.List(Of T)` である。

`List(Of T)` は、内部で「配列」を隠蔽しつつ、動的配列のパフォーマンス問題をエレガントに解決している。その核心は 「アロケーションの倍加戦略(Capacity Doubling)」 にある。

`List(Of T)` は要素を追加していく際、内部配列の容量(`Capacity`)が一杯になると、現在のサイズの2倍のメモリ領域を新たに確保し、一括コピーを行う。
これにより、要素追加における償却計算量(Amortized Time Complexity)は $O(1)$ にまで劇的に最適化される。

`List(Of T)` を用いた最適化コード

先ほどの処理を、`List(Of T)` を使って書き換えた極限まで効率的な実装が以下だ。

.net
Imports System.Collections.Generic
Imports System.Diagnostics

Module ListOptimization
Sub Main()
Const iterations As Integer = 50000

‘ 【極意】あらかじめ最終的な規模が推測できる場合は Capacity を指定せよ
‘ 無駄なメモリ再割り当てとコピーを完全に排除できる。
Dim dataList As New List(Of Integer)(iterations)

Dim sw As Stopwatch = Stopwatch.StartNew()

For i As Integer = 0 To iterations – 1
dataList.Add(i)
Next

sw.Stop()
Console.WriteLine($”List(Of T) 実行時間: {sw.ElapsedMilliseconds} ms”)
End Sub
End Module

さらに言うならば、あらかじめデータ数が予測できる業務システム(例えば、SQL Serverから取得するレコード数がおおむねわかっている場合など)であれば、コンストラクタで `Capacity` を明示的に指定するべきだ。
これにより、内部配列の拡張処理(リサイズによる再割り当て)すら完全にゼロに抑え込み、パフォーマンスを極限まで引き出すことができる。

4. レガシーシステム保守とシステム間連携における実務的判断基準

シニアエンジニアやシステム管理者が直面するのは、往々にして「他システム連携のCSV出力」や「古いVB6製COMコンポーネントとのインタフェース」といったレガシーな境界領域である。

これらの現場において、以下の基準でコレクションの選択とコードの近代化を断行せよ。

コレクション選択の黄金律

1. データ数が完全に静的(イミュータブル)な場合

  • 例:曜日マスター、設定ファイルの固定項目など。
  • 選択:固定長配列 (`Dim arr(N) As Type`)。オーバーヘッドが最も少なく型安全。

2. データ数が動的に変動し、逐次追加していく場合

  • 例:ファイル読み込み、DBからのストリーミング取得、ログ解析。
  • 選択:`List(Of T)` 一択。`ReDim Preserve` はコードベースから抹消せよ。

3. 高速な検索や重複排除が必要な場合

  • 選択:`Dictionary(Of TKey, TValue)` や `HashSet(Of T)`。配列を線形探索(`Array.IndexOf`)する愚を犯してはならない。

結言:コードの美しさは、メモリへの敬意から宿る

プログラミングとは、突き詰めると「ハードウェア資源の調停」である。
Visual Basicという言語は、その歴史的背景から「誰でも簡単に書ける」という側面を強調されがちだが故に、メモリの裏側で何が起きているかを隠蔽しすぎるきらいがある。

しかし、プロのアーキテクトたる者、表面的な構文の容易さに甘えてはならない。
`ReDim Preserve` の背後でうごめくメモリコピーの重みを感じ取り、適切なデータ構造を選択すること。その積み重ねこそが、過酷なエンタープライズ環境でも沈まない、堅牢で高パフォーマンスなシステムを作り上げる唯一の道なのである。

さあ、今すぐ君のソリューションエクスプローラーを開き、不要な `ReDim Preserve` を駆逐したまえ。

タイトルとURLをコピーしました