`ReDim Preserve`は、VBAの動的配列をサイズ変更しながら、すでに入っている値を残したいときに使います。通常の`ReDim`だけで再割り当てすると既存の要素は初期化されます。まず覚えておきたい制限は、`Preserve`を付けた場合に変更できるのは**最後の次元の上限だけ**という点です。2次元以上の配列では、先頭側の次元をそのまま拡張することはできません。
まず結論:ReDimとReDim Preserveは「値を残すか」が違う
| 書き方 | 既存の値 | サイズ変更 | 主な用途 |
|---|---|---|---|
| ReDim arr(…) | 保持しない | 動的配列を再割り当てする | 最初のサイズ決定・値を捨てて作り直す |
| ReDim Preserve arr(…) | 保持する | 最後の次元の上限のみ変更できる | 値を残したまま末尾側へ要素を増やす |
MicrosoftのVBAリファレンスでは、`Preserve`は既存データを保持しながら最後の次元をサイズ変更するためのキーワードと説明されています。動的配列は`Dim items() As String`のように、宣言時に要素数を決めずに用意します。
用語解説:動的配列
実行中にReDimで要素数を決めたり変更したりできる配列です。宣言時は`Dim items() As String`のように括弧内を空にします。
1次元配列をReDim Preserveで拡張する基本形
Sub ReDimPreserveBasic()
Dim items() As String
ReDim items(0 To 1)
items(0) = "A"
items(1) = "B"
ReDim Preserve items(0 To 2)
items(2) = "C"
Debug.Print items(0), items(1), items(2)
End Sub
最初に0~1の2要素を確保し、AとBを入れています。その後`ReDim Preserve items(0 To 2)`で上限を2へ広げると、AとBを残したまま新しい要素`items(2)`を使えます。
同じ場所を通常の`ReDim items(0 To 2)`にすると、以前のAとBは保持されません。『サイズだけ変えたいが、今の値は残したい』ときにPreserveを付けるのが基本です。
UBoundを使えば現在の末尾から1要素増やせる
Sub AppendWithReDimPreserve()
Dim items() As String
Dim nextIndex As Long
ReDim items(0 To 1)
items(0) = "A"
items(1) = "B"
nextIndex = UBound(items) + 1
ReDim Preserve items(LBound(items) To nextIndex)
items(nextIndex) = "C"
End Sub
配列の現在サイズを決め打ちせず、`UBound(items)`で上限を取得してから1つ増やす形です。下限も`LBound(items)`をそのまま使うため、Preserveで下限を変更してしまう事故を避けられます。
(LBound・UBoundを含むVBA配列の基礎から確認したい場合については『VBA配列の使い方完全ガイド|高速化の仕組みとエラー対策まで』をご参照ください)
2次元配列では最後の次元だけ変更できる
`ReDim Preserve`で最もつまずきやすいのが多次元配列です。Microsoftの公式仕様では、Preserveを使う場合は最後の次元だけをサイズ変更でき、次元数そのものも変えられません。
Sub ReDimPreserve2D()
Dim data() As String
ReDim data(1 To 3, 1 To 2)
data(1, 1) = "A"
' OK: 最後の次元の上限を2から4へ変更
ReDim Preserve data(1 To 3, 1 To 4)
Debug.Print data(1, 1)
End Sub
この例では第1次元`1 To 3`を変えず、第2次元だけを`1 To 4`へ広げています。既存値を保持したまま変更できます。
' NG: Preserveのまま第1次元を変更する
ReDim Preserve data(1 To 5, 1 To 2)
上のように第1次元を変更すると、Preserveの制約に反します。行方向を増やしたい設計では、増やしたい軸を最後の次元に置けるか、最終サイズを先に求めて一度だけReDimできるかを検討します。
Preserveでは下限を変更できない
もう1つ重要なのが下限です。Preserveを使うサイズ変更では、既存の下限を別の値に変えることはできません。変更するのは最後の次元の**上限**です。
Sub LowerBoundExample()
Dim values() As Long
ReDim values(1 To 3)
values(1) = 10
' OK: 下限1は維持し、上限だけ5へ変更
ReDim Preserve values(1 To 5)
' NG: 下限を0へ変更しながらPreserveする
' ReDim Preserve values(0 To 5)
End Sub
下限を安全に維持したい場合は、前の例のように`LBound(values)`を使うと意図がコードに残ります。
配列を小さくすると、外れた要素は戻らない
Preserveは既存データをできるだけ保持しますが、縮小して範囲外になった要素まで残す機能ではありません。Microsoftの説明でも、元のサイズより小さくすると、削除対象になった要素のデータは失われるとされています。
Sub ShrinkArray()
Dim values() As Long
ReDim values(0 To 4)
values(4) = 99
ReDim Preserve values(0 To 2)
' values(4) は配列の範囲外になり、保持されない
End Sub
一時的に小さくして後で元へ戻せば値も戻る、という動きではありません。縮小前に必要な値が範囲外へ出ないかを確認します。
固定サイズ配列ではなく、動的配列として宣言する
実行中にサイズを変える予定なら、宣言時に要素数を固定せず、空の括弧で動的配列として宣言します。
Dim dynamicItems() As String
ReDim dynamicItems(0 To 9)
`Dim fixedItems(0 To 9) As String`のように固定サイズで宣言した配列を、後からReDimする設計とは分けて考えます。必要な要素数が実行時まで決まらないケースで、動的配列とReDimを使います。
ReDim Preserveで迷ったときの確認順
- 配列が`Dim items()`のような動的配列として宣言されているか
- 既存値を残す必要があるなら`Preserve`が付いているか
- 2次元以上なら変更しているのが最後の次元か
- Preserveの前後で下限を変えていないか
- 縮小する場合、切り捨てられる要素に必要な値がないか
- サイズを決め打ちせず追加するならLBound・UBoundで現在範囲を確認しているか
エラーが出たら、まず`ReDim Preserve`の括弧内を前回の配列範囲と並べてください。『最後の次元以外を変えていないか』『下限まで変えていないか』を見ると、原因をかなり絞れます。
よくある質問
ReDim Preserveを使えば何次元でも自由にサイズ変更できますか?
できません。Preserveを使う場合、変更できるのは最後の次元の上限です。2次元以上では、それより前の次元を変更したまま既存値を保持することはできません。
ReDimだけだと本当に値は消えますか?
はい。通常のReDimで再割り当てすると既存の配列要素は保持されず、型に応じた初期値になります。既存値を残す必要がある場合はPreserveを使います。
ReDim Preserveで配列の開始番号を0から1へ変えられますか?
Preserveを使ったサイズ変更では下限を変更できません。開始番号を変える必要がある場合は、既存配列を保持したまま単純にReDim Preserveする方法では対応できません。
要素を1件追加するたびにReDim Preserveしてもよいですか?
動作上は可能ですが、最終件数が分かるなら先に必要なサイズを確保するとコードの意図が明確です。件数が読めない場合は、UBoundで現在の上限を確認して拡張するパターンを使えます。
まとめ:値を残すならPreserve、ただし変更できる次元に注意
- 通常のReDimは動的配列を再割り当てし、既存値を保持しない
- ReDim Preserveは既存値を残してサイズ変更できる
- Preserveで変更できるのは最後の次元の上限だけ
- Preserveのまま下限や次元数を変更できない
- 縮小で範囲外になった要素のデータは失われる
手元のコードで値が消える場合は、ReDimの前後で配列範囲を確認し、値を残す必要がある箇所だけPreserveへ置き換えられるか確認してみてください。