配列か、List(Of T)か。VB.NETのメモリ管理を極める「生存戦略」
現場でコードを見ていると、脊髄反射で「とりあえず配列」あるいは「とりあえずList」を使っているケースが後を絶たない。VB.NETという言語は、親しみやすさゆえに「裏で何が起きているか」を隠蔽しがちだ。しかし、システムが数万件、数十万件のレコードを扱うようになった瞬間、その「隠蔽」が致命的なボトルネックとなって牙を剥く。
今回は、伝説的アーキテクトの視点から、配列と`List(Of T)`の境界線、そしてメモリ効率を最大化する設計思想を伝授する。
—
1. 根本的な違い:静的な「箱」と動的な「管理機構」
配列 (Array)
メモリ上に連続した固定領域を確保する。インデックスアクセスは最強に速い。しかし、一度確保したサイズは変更できない。サイズを変えるには、新しい配列を確保し、全要素をコピーする(`ReDim Preserve`)必要がある。これは巨大なデータセットでは自殺行為に近いコストだ。
List(Of T)
内部的には配列を保持しているが、「容量(Capacity)」と「要素数(Count)」を分離管理している。要素を追加する際、容量が足りなくなれば、内部で2倍のサイズの配列を再確保し、旧配列をコピーする仕組みだ。
—
2. どちらを選ぶべきかの「極限の判断基準」
実務においての選択基準は、以下の3点に集約される。
1. 要素数が確定しているか?
- Yes:迷わず配列。メモリ効率・速度ともに最強。
- No:List(Of T)。
2. 頻繁な検索が必要か?
- インデックス指定のアクセスが主なら配列。複雑な抽出条件が必要ならLINQと相性の良いList。
3. データ寿命は?
- メソッド内の一時的な計算なら配列。クラスのプロパティとして保持し、追加・削除が繰り返されるならList。
—
3. 実装の鉄則:メモリ断片化を防ぐ「Capacityの事前確保」
`List(Of T)`を使う際、最も犯しやすいミスが「容量の初期化を怠ること」だ。デフォルトのListは、要素が追加されるたびに内部で何度も配列の再確保(リサイズ)を行い、メモリを浪費する。
もし、処理対象の件数が予測できるなら、必ず`Capacity`を指定せよ。
.net
”’
”’
Public Sub ProcessLargeData(sourceData As IEnumerable(Of String))
‘ 件数が予測できる場合、Capacityを先に指定する
‘ これにより、実行時の内部配列再確保(コピー処理)をゼロに抑える
Dim count As Integer = sourceData.Count()
Dim resultList As New List(Of String)(count)
For Each item In sourceData
‘ このAdd処理は非常に高速。再確保が発生しないため。
resultList.Add(item)
Next
End Sub
—
4. プロダクションコード:保守性と堅牢性を両立する設計
データベースやファイルから読み込む際、Listで加工し、最終的に固定データとして扱うなら、最後に`ToArray()`で変換するのが鉄則だ。これにより、外部への不用意な変更を遮断できる。
.net
Public Function GetProcessedData(rawFilePath As String) As String()
‘ 読み込み行数が不明な場合はListで受ける
Dim buffer As New List(Of String)()
Using reader As New System.IO.StreamReader(rawFilePath)
While Not reader.EndOfStream
Dim line As String = reader.ReadLine()
‘ バリデーション:堅牢なシステムは入力で弾く
If Not String.IsNullOrWhiteSpace(line) Then
buffer.Add(line.Trim())
End If
End While
End Using
‘ 呼び出し元には「変更不可」な配列として返すのが作法
Return buffer.ToArray()
End Function
—
5. アーキテクトからの助言:なぜこの書き方が「正義」なのか
- なぜ `List.ToArray()` を使うのか?
Listをそのまま戻り値にすると、呼び出し元で`.Add()`や`.Clear()`ができてしまう。これは「データの完全性」を破壊する温床だ。戻り値は配列(あるいは`IEnumerable`)に固定し、内部の可変性をカプセル化する。これがバグを未然に防ぐ防御的プログラミングだ。
- メモリ効率の観点
もし要素が100万件あるなら、`List(Of T)`をそのまま保持し続けることは、配列の約2倍のメモリを食いつぶす可能性がある(Capacityが要素数よりも多めに確保されるため)。用途が終われば`Clear()`を呼び出し、参照を`Nothing`に倒すことでGC(ガベージコレクション)を促す気配りが必要だ。
まとめ
- 固定長なら配列。 迷う必要はない。
- 可変長ならList。 ただし、件数が予測できるなら必ずコンストラクタで`Capacity`を確保せよ。
- クラスの境界線をまたぐときは配列化。 内部の可変性を外部に漏らさない。
プロのコードとは、単に動くものではない。「メモリの挙動を支配し、未来のバグを設計段階で排除したもの」を指す。今日から、君のコードのパフォーマンスは劇的に変わるはずだ。
