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

スポンサーリンク

VB.NET配列の呪縛を断ち切れ:`ReDim Preserve`の罠と、モダンな開発者が選ぶべき本当のコレクション

開発現場のリーダーとして、日々後輩たちの書くコードレビューをしていると、今でも昭和の遺物のようなコードに出くわすことがある。
その最たるものが、「動的配列のサイズ変更をループ内で繰り返すコード」だ。

「データ数が分からないから、とりあえず配列を作って、データが来るたびに `ReDim Preserve` で広げればいいか」

もしあなたがこの思想でコードを書いているなら、今すぐその手を止めてほしい。その書き方は、あなたの作っている業務ツールのパフォーマンスを静かに、しかし確実に蝕んでいる。

今回は、VB.NETにおける配列の初期化の基本から、`ReDim Preserve` が引き起こすメモリ破壊的なパフォーマンス低下のメカニズム、そしてプロが実務で使うべき `List(Of T)` への移行基準まで、容赦ないロジックと実用コードで解説する。

1. なぜ `ReDim Preserve` は「やってはいけない」のか?

まずは、VB.NETの動的配列の裏側で何が起きているかを知る必要がある。
配列(Array)の本質は、「メモリ上に連続した領域を確保するデータ構造」だ。

ここに `ReDim Preserve` を使うと、VB.NETのランタイム(CLR)は裏で以下のような重い処理を強制される。

1. 新しいサイズの連続したメモリ領域を新しく確保する
2. 古いメモリ領域から、新しいメモリ領域へ全要素を1件ずつコピーする
3. 古いメモリ領域を破棄する(ガベージコレクションの負荷になる)

これをデータ件数分、例えばループの中で1万回繰り返したとしたらどうなるか?
「$O(N^2)$ の計算量」となり、データが増えれば増ほど指数関数的に処理が遅くなる。これが、業務システムをフリーズさせる「動くゴミコード」の正体だ。

2. 比較検証:非効率な `ReDim Preserve` vs 爆速な `List(Of T)`

百聞は一見に如かず。CSVファイルやデータベースから読み込んだ膨大なレコード(ここでは例として10万件とする)を処理するシーンを想定して、両者のアプローチを比較してみよう。

【アンチパターン】ループ内 `ReDim Preserve`

‘ 【絶対に真似してはいけないアンチパターン】
Dim results() As String = {}
Dim count As Integer = 0

‘ 外部から10万件のデータを受け取る仮定のループ
For i As Integer = 1 To 100000
‘ 毎回配列を拡張(メモリの再割り当てと全コピーが発生)
ReDim Preserve results(count)
results(count) = “Data_” & i
count += 1
Next

このコードは、数万件を超えたあたりから急激にファンが回りだし、処理が重くなる。実務の現場では「サーバーが応答しない」「Excelマクロからの移行なのに遅い」というクレームの温床になる。

3. プロの解答:`List(Of T)` による圧倒的なパフォーマンス最適化

サイズが事前に分からないコレクションを扱う場合、.NET Framework / .NET Core が提供するジェネリックコレクション `List(Of T)` を使うのが鉄則だ。

`List(Of T)` の内部実装は、実は「配列」である。しかし、要素がいっぱいになったときだけ、あらかじめ大きめのメモリ(キャパシティ)を倍々に確保し直す戦略(アモタイズド分析による $O(1)$ の追加コスト)をとっているため、都度コピーが発生する非効率さを回避している。

【推奨パターン】`List(Of T)` を用いた堅牢な実装

それでは、ファイル(CSVやテキスト)読み込みとデータベース連携を想定した、実務でそのまま使えるプロダクションコードを提示する。

Imports System.IO
Imports System.Collections.Generic
Imports System.Text

Public Class DataProcessor

”’

”’ 大容量ファイルを安全かつ高速に読み込み、リストに格納するサンプル
”’

”’ 読み込むファイルのパス ”’ 処理済みデータのリスト
Public Function LoadAndProcessData(filePath As String) As List(Of String)

‘ 1. 型安全かつ高速な List(Of T) を初期化
‘ ※あらかじめレコード数が概算で分かっている場合は New List(Of String)(100000) のように
‘ 初期キャパシティを指定すると、内部配列の再確保コストすら削れる。
Dim dataList As New List(Of String)()

‘ ファイルが存在するかチェック(堅牢な設計の基本)
If Not File.Exists(filePath) Then
Throw New FileNotFoundException(“指定されたデータファイルが見つかりません。”, filePath)
}

‘ 2. Usingブロックでストリームを確実に解放(リソースリークの防止)
‘ 文字コードは環境に合わせて変更すること(Shift_JISなら Encoding.GetEncoding(“shift_jis”))
Using sr As New StreamReader(filePath, Encoding.UTF8)

While Not sr.EndOfStream
Dim line As String = sr.ReadLine()

‘ データのバリデーション(空行やコメント行をスキップ)
If String.IsNullOrWhiteSpace(line) OrElse line.StartsWith(“#”) Then
Continue While
End If

‘ ビジネスロジックの適用(例:カンマ区切りの整形など)
Dim processedValue As String = TransformData(line)

‘ 3. Addメソッドで高速に追加(ReDim不要)
dataList.Add(processedValue)
End While

End Using

‘ 必要であれば、最終的に配列に戻すことも可能(ToArray() は一回だけ走るため効率的)
‘ Dim finalArray() As String = dataList.ToArray()

Return dataList
End Function

”’

”’ 個別のデータ変換ロジック
”’

Private Function TransformData(rawLine As String) As String
‘ ここに実際のパース処理やDB連携前の前処理を記述
Return rawLine.Trim()
End Function

End Class

4. データベース連携における注意点

業務システムでデータベース(SQL Server, Oracle, SQLite等)からデータを取得する際も同様だ。
`Command.ExecuteReader()` で取得したデータを配列に詰め込もうとして `ReDim Preserve` を使ってはならない。

データベースから取得する件数が数千〜数万件規模になることが確実な場合は、以下のように `List(Of T)` にマッピングするのが定跡だ。

‘ データベースから取得したエンティティを格納するリスト
Dim userList As New List(Of UserEntity)()

Using connection As New SqlConnection(connectionString)
connection.Open()
Using command As New SqlCommand(“SELECT UserId, UserName FROM M_User”, connection)
Using reader As SqlDataReader = command.ExecuteReader()

While reader.Read()
Dim user As New UserEntityWith
{
.UserId = reader.GetInt32(0),
.UserName = reader.GetString(1)
}

‘ 一瞬でリストに追加
userList.Add(user)
End While

End Using
End Using
End Using

これにより、メモリの断片化を防ぎ、GC(ガベージコレクション)のプレッシャーを最小限に抑えることができる。

5. リーダーからの総括:配列とコレクションの使い分け基準

最後に、今後の設計の指針となる「使い分けのルール」を明文化する。

1. 要素数が絶対に変わらない(定数的なもの、固定長マスターなど)

  • $\rightarrow$ 固定長配列 (`Dim items(9) As Integer` など) を使ってよい。メモリ効率・速度ともに最速。

2. 要素数が動的に変化する(ファイル読み込み、画面からの動的入力、DB検索結果など)

  • $\rightarrow$ 問答無用で `List(Of T)` を採用せよ。 `ReDim Preserve` は封印する。

3. どうしても配列型でAPIに渡す必要がある場合

  • $\rightarrow$ `List(Of T)` でデータを構築しきったあとに、最後に一度だけ `.ToArray()` を呼んで配列に変換せよ。

「動的配列だから `ReDim Preserve`」という思考停止の呪縛を解き放ち、モダンでスケーラブルなコードを書くこと。それが、保守性が高くトラブルの起きない業務システムを作り上げるエンジニアの流儀である。

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