はじめに
これは Visual Basic Advent Calendar 2025 2日目の記事です。
実行環境
Windows 11 Pro 64bit
Microsoft 365 の Excel (64bit)
配列で処理の高速化
Excel VBAでは、連続したセル範囲に同じ処理をするケースがよくあります。For~Next で10万行を超えるループを書いた結果、まるで画面が固まってしまったかのように動作が遅くなる経験をした方もいることでしょう。
10万行以上の処理など、VBAには向かないのでしょうか。
いえ、そんなことはありません。
処理速度を大きく改善するための代表的な方法が、配列の利用です。
VBAではセルの読み書きを大量に行うと、処理速度が大きく低下します。ループ内でセル操作を繰り返さず、処理は配列ですべて済ませて、その結果をセル範囲に渡す方法は、VBAの定石ともいえる高速化の手法です![]()
VBAの配列
配列の宣言は通常以下のように行います。要素数を決めて宣言する場合と、要素数を宣言後に決める場合があります。後者を動的配列と言います。
Rem 要素数を決めて宣言する場合
Dim arr1(9) ' インデックスは0から9で要素数は10個
Dim arr2(1 To 10) ' インデックスは1から10で要素数は10個
Rem 要素数を決めずに宣言(動的配列)
Dim arr3()
Dim arr4() As String ' As句でString型を明示的に宣言(メモリ効率は良い)
' ReDimで要素数を決める
ReDim arr3(4) ' As句を省略するとVariant型になる
ReDim arr4(1 To 500) ' As句を省略して宣言時の型を踏襲する
Rem Variant型の配列の要素は型を混在できる
arr3(0) = "Hoge" ' String
arr3(1) = 1 ' Integer
arr3(2) = 0.3 ' Double
arr3(3) = Now ' Date
arr3(4) = True ' Boolean
これらの配列は一次元配列です。配列は次元を増やせます。次元とはどういうことか?
二次元配列の構造を見ればイメージがつかめると思います。
以下のようにインデックスを1始まりで二次元配列を宣言してみましょう。
二次元目以降の要素数はカンマで区切って宣言します。
Rem 2次元目以降の要素数はカンマ[,]で区切って宣言
Dim arr(1 To 8, 1 To 4) ' 1次元目は、1から8、2次元目は、1から4の要素を持たせる
この二次元配列の構造は以下の通りです。
| 1 | 2 | 3 | 4 | |
|---|---|---|---|---|
| 1 | arr(1,1) | arr(1,2) | arr(1,3) | arr(1,4) |
| 2 | arr(2,1) | arr(2,2) | arr(2,3) | arr(2,4) |
| 3 | arr(3,1) | arr(3,2) | arr(3,3) | arr(3,4) |
| 4 | arr(4,1) | arr(4,2) | arr(4,3) | arr(4,4) |
| 5 | arr(5,1) | arr(5,2) | arr(5,3) | arr(5,4) |
| 6 | arr(6,1) | arr(6,2) | arr(6,3) | arr(6,4) |
| 7 | arr(7,1) | arr(7,2) | arr(7,3) | arr(7,4) |
| 8 | arr(8,1) | arr(8,2) | arr(8,3) | arr(8,4) |
ご覧のとおり、まるでWorksheetオブジェクトのCellsプロパティのように見えます。一次元目のインデックスを行番号、二次元目のインデックスを列番号と見立てることができます。
この配列に値が格納されているとして、その値をセル範囲に代入することを考えてみましょう。それには配列の要素数を知る必要があります。
配列の要素数は以下のように取得します。LBound関数でインデックスの下限、UBound関数でインデックスの上限を得られます。
Rem 一次元配列のインデックスの取得
Dim arr1(99)
Dim lBnd As Long: lBnd = LBound(arr1) ' lBndは、0(インデックスの最小値)
Dim uBnd As Long: uBnd = UBound(arr1) ' uBndは、99(インデックスの最大値)
Rem 二次元配列のインデックスの取得
Dim arr2(1 To 10, 1 To 5)
Dim lBnd1 As Long: lBnd1 = LBound(arr2, 1) ' lBnd1は、1
Dim uBnd1 As Long: uBnd1 = UBound(arr2, 1) ' uBnd1は、10
Dim lBnd2 As Long: lBnd2 = LBound(arr2, 2) ' lBnd2は、1
Dim uBnd2 As Long: uBnd2 = UBound(arr2, 2) ' uBnd2は、5
二次元配列の場合、LBound関数及びUBound関数の第二引数にどの次元を測りたいかを指定します。
この知識を前提に、配列の内容をセル範囲へ反映するには、出力したいセル範囲の左上端セルを基点に、RangeオブジェクトのResizeプロパティで行列サイズを配列のサイズに合わせて拡張します。
Rem Resizeで二次元配列のサイズに合わせてセル範囲を拡大する
Sheet1.Cells(1, 1).Resize(UBound(arr, 1), UBound(arr,2)).Value = arr ' .Valueを忘れずに
セル範囲への反映は、この代入の一行で済んでしまいます。ループして1要素ずつ値を設定する必要はありません。これは簡単ですね![]()
配列に化ける便利なVariant型
配列は、Variant型の変数(Dim arr()ではなく、単にDim arrで)でも作れます。以下のようにEmptyを絡めると意外に便利です。
Rem Varinat型の変数で動的配列を作る
Dim arr As Variant ' () を書かない
ReDim arr(1 To 100, 1 To 5) ' Variant型が二次元配列に化ける
' 処理1
'(条件によって、arr変数は全く使用されない場合があるとする)
Rem 例えば、IsEmpty関数でarrが使われたかどうか判定(Emptyなら未使用)
If IsEmpty(arr) Then GoTo Finally
' 処理2
'(arrを使用した処理)
Rem 使用後にarr変数を無効化できる
arr = Empty ' arr変数が、カッコが付いた配列、arr() として宣言されていたらエラー
- 初期化されていない状態を
IsEmpty関数で判定できる -
Emptyを代入して変数をリセットできる- ただし、
arrがarr()と宣言されていたらエラーになる
- ただし、
ちなみに変数が配列の場合の無効化は以下でも可能です。
Rem 一般的な配列変数の無効化
Erase arr
これはarr変数が配列に化けていれば使えますが、配列ではない場合はエラーとなります。
Variant型変数の無効化の場合、Erase arrよりも arr = Empty の方がこのエラーを気にせず使えるのでお勧めです。
Emptyを代入することで、変数をまったく空っぽのVariant型に戻すことができます(配列化もリセットされる)。
IsEmpty関数
先ほどのサンプルコードに登場したIsEmpty関数は、VBAの組み込み関数で、Variant型の引数を受け、引数がEmptyかどうかを判定します。以下はVBAの様々な組み込み型の変数に値を代入する前にIsEmpty関数の戻り値がどうなるかのテストです。
Dim varArr As Variant: Debug.Print "-- Variant型 --"
Debug.Print "代入前: IsEmpty(varArr) = " & IsEmpty(varArr)
Dim strArr As String: Debug.Print "-- String型 --"
Debug.Print "代入前: IsEmpty(strArr) = " & IsEmpty(strArr)
Dim intArr As Integer: Debug.Print "-- Integer型 --"
Debug.Print "代入前: IsEmpty(intArr) = " & IsEmpty(intArr)
Dim dblArr As Double: Debug.Print "-- Double型 --"
Debug.Print "代入前: IsEmpty(dblArr) = " & IsEmpty(dblArr)
Dim dateArr As Date: Debug.Print "-- Date型 --"
Debug.Print "代入前: IsEmpty(dateArr) = " & IsEmpty(dateArr)
Dim boolArr As Boolean: Debug.Print "-- Boolean型 --"
Debug.Print "代入前: IsEmpty(boolArr) = " & IsEmpty(boolArr)
値を代入する前なので期待する戻り値は、空っぽ(Empty)であるという意味でTrueを予測しますが、以下のとおり、Trueを返すのはVariant型だけです。
-- Variant型 --
代入前: IsEmpty(varArr) = True
-- String型 --
代入前: IsEmpty(strArr) = False
-- Integer型 --
代入前: IsEmpty(intArr) = False
-- Double型 --
代入前: IsEmpty(dblArr) = False
-- Date型 --
代入前: IsEmpty(dateArr) = False
-- Boolean型 --
代入前: IsEmpty(boolArr) = False
※当然ながら、代入後はどの型もFalseを返す
このテストからもわかるとおり、IsEmpty関数はVariant型のための組み込み関数となります。
また、配列とは離れますが、任意のセルが空かどうかを判定するとき、IsEmpty関数はEmpty値を使った論理式より正確です。
例えば、A1セルが選択されていて、そこに0が入力されているとき、
Debug.Print "(ActiveCell.Value = Empty) : " & (ActiveCell.Value = Empty)
Debug.Print "IsEmpty(ActiveCell.Value) : " & IsEmpty(ActiveCell.Value)
(ActiveCell.Value = Empty) : True
IsEmpty(ActiveCell.Value) : False
※A1の 0 をDeleteキーで消去した場合、IsEmpty(ActiveCell.Value) は、True
Empty値での論理式だと0をEmptyと判定してしまいますが、IsEmpty関数だと、セルに0が入力されているか、まったくの空なのか、を判定できます。
配列を返す関数とメソッド
以下は、よく使われる配列を返す関数とメソッドです。
-
Array 関数
任意の値を引数で受け取り、それらを格納したVariant型の一次元配列を返す -
Split 関数
文字列を区切り文字で分割し、結果をString型の一次元配列で返す -
Dictionary.Keys メソッド
Dictionaryに登録されているキーの一覧をVariant型の一次元配列で返す -
Dictionary.Items メソッド
Dictionaryに登録されている値の一覧をVariant型の一次元配列で返す
以下、これらの関数やメソッドを使ってみます。いずれも戻り値の配列をVariant型の変数で受けとれます。
Dim arr As Variant
Rem Array関数の戻り値を受ける
arr = Array(10, 20, 30)
Debug.Print "--- arr = Array(10, 20, 30) ---"
Debug.Print "arr(0) = " & arr(0)
Debug.Print "arr(1) = " & arr(1)
Debug.Print "arr(2) = " & arr(2)
Debug.Print Empty
Rem arr変数をSplit関数の戻り値で上書き
arr = Split("40,50,60", ",")
Debug.Print "--- arr = Split(""40,50,60"", "","") ---"
Debug.Print "arr(0) = " & arr(0)
Debug.Print "arr(1) = " & arr(1)
Debug.Print "arr(2) = " & arr(2)
Debug.Print Empty
Rem Dictionaryにアイテムを追加
Dim dic As Object: Set dic = CreateObject("Scripting.Dictionary")
dic.Add "A", 1
dic.Add "B", 2
dic.Add "C", 3
Debug.Print "--- dic.Add ---"
Debug.Print "dic(""A"") = " & dic("A")
Debug.Print "dic(""B"") = " & dic("B")
Debug.Print "dic(""C"") = " & dic("C")
Debug.Print Empty
Rem arr変数をDictionaryのKeysメソッドの戻り値で上書き
arr = dic.Keys
Debug.Print "--- arr = dic.Keys ---"
Debug.Print "arr(0) = " & arr(0)
Debug.Print "arr(1) = " & arr(1)
Debug.Print "arr(2) = " & arr(2)
Debug.Print Empty
Rem arr変数をDictionaryのItemsメソッドの戻り値で上書き
arr = dic.Items
Debug.Print "--- arr = dic.Items ---"
Debug.Print "arr(0) = " & arr(0)
Debug.Print "arr(1) = " & arr(1)
Debug.Print "arr(2) = " & arr(2)
--- arr = Array(10, 20, 30) ---
arr(0) = 10
arr(1) = 20
arr(2) = 30
--- arr = Split("40,50,60", ",") ---
arr(0) = 40
arr(1) = 50
arr(2) = 60
--- dic.Add ---
dic("A") = 1
dic("B") = 2
dic("C") = 3
--- arr = dic.Keys ---
arr(0) = A
arr(1) = B
arr(2) = C
--- arr = dic.Items ---
arr(0) = 1
arr(1) = 2
arr(2) = 3
ここで一点注目すべきところは、何度も繰り返されるarr変数への代入の部分です。
もともと配列だったarr変数をたった1行で全く別の配列に上書きできてしています。これは便利な反面、バグの温床にもなりますので注意が必要です。
これらの関数とメソッドが返す配列のインデックスは0始まりとなります。配列の下限インデックスを設定するOption Base 1が宣言されていても、Array関数以外は0のまま影響を受けません。
Rem モジュールの先頭で宣言
Option Base 1
中途半端で混乱の元になるので、このステートメントは宣言しないことをお勧めします。
RangeオブジェクトのValueプロパティ
また、セル範囲への書き込みとは逆に、セル範囲の値を二次元配列として読み込むことも可能です。
Dim arr As Variant
Rem セル範囲の値をVariant型変数に一行で代入
arr = Sheet1.Range("A1:B4").Value ' 代入後、arr変数はVariant型の二次元配列になる
Dim rw As Long
For rw = 1 To UBound(arr, 1) ' インデックスは1始まり
Debug.Print arr(rw, 1) & " | " & arr(rw, 2)
Next
商品名 | 価格
りんご | 698
みかん | 498
いちご | 798
この場合、配列のインデックスは1始まりとなります。
その他、注意すべきこと
- 配列を返す関数・メソッドの戻り値は、インデックスが固定された配列には代入できない
Rem 静的配列への代入はVariant型であっても不可 Dim arr(2) As Variant arr = Array(10, 20, 30) ' コンパイルエラーなのでプロシージャ実行前にエラーとなる
-
以下を実行
Rem 現在のセル値と同じ値の配列を準備して代入するテスト Dim arr As Variant arr = Array("値", 1, 2, 3, 4, 5) ' 一次元配列(横方向になる) Sheet2.Range("A1:A6").Value = WorksheetFunction.Transpose(arr) ' 縦方向に変換して代入これは
Transposeに問題があるのではなく、あくまでもフィルタの影響です。
Transposeには別の問題があります。-
WorksheetFunction.Transpose メソッド
- 配列の縦横を入れ替えて
Variant型の二次元配列で返す - 戻り値のインデックスは1始まりになる
- 転置後の行数が 65,536 を超えるとエラー
- 要素内に
NULLが含まれるとエラー
- 配列の縦横を入れ替えて
最近では
Application.Transposeというものがあるらしく、基本的にはWorksheetFunction.Transposeのラッパーで、転置時にエラーがあっても実行時エラーで止まることなくエラー値を返せるそうです(ChatGPTの説明による。違っていたらすみません
)。ここではそういうものがあるという言及だけに留めておきます。配列の縦横転置は難しくないので、自作してもいいでしょう。
Rem 配列の行列を転置する Public Function TransposeArr(arr As Variant) As Variant Rem 配列の次元数を判定 Select Case GetDimensions(arr) Rem 一次元配列の場合 Case 1 Dim lBnd As Long: lBnd = LBound(arr, 1) Dim uBnd As Long: uBnd = UBound(arr, 1) Rem ゼロベースの上限インデックス Dim maxIdx As Long: maxIdx = uBnd - lBnd Rem 転置のため、ゼロベースの二次元配列を準備 Dim arr1 As Variant: ReDim arr1(maxIdx, 0) Dim idx As Long: idx = 0 Rem 縦方向に転置 Dim i As Long For i = lBnd To uBnd arr1(idx, 0) = arr(i) idx = idx + 1 Next TransposeArr = arr1 Rem 二次元配列の場合 Case 2 Dim lBnd1 As Long: lBnd1 = LBound(arr, 1) Dim uBnd1 As Long: uBnd1 = UBound(arr, 1) Dim lBnd2 As Long: lBnd2 = LBound(arr, 2) Dim uBnd2 As Long: uBnd2 = UBound(arr, 2) Dim arr2 As Variant: ReDim arr2(lBnd2 To uBnd2, lBnd1 To uBnd1) Rem 縦横転置 Dim c As Long, r As Long For c = lBnd2 To uBnd2 For r = lBnd1 To uBnd1 arr2(c, r) = arr(r, c) Next Next TransposeArr = arr2 Rem 二次元を超える場合 Case Else ' 何もしない End Select End Function Rem 配列の次元数を取得 Public Function GetDimensions(arr As Variant) As Long On Error GoTo Catch Dim dimNum As Long: dimNum = 0 Dim i As Long, rt As Long For i = 1 To 60 ' VBAの最大次元数は 60 rt = LBound(arr, i) dimNum = dimNum + 1 Next Catch: GetDimensions = dimNum End Functionいずれにしても、フィルタの影響は受けますが、
Transposeの制約は無くなります。 -
WorksheetFunction.Transpose メソッド
さいごに
宣言時のAs句による明示的な型指定は、メモリ効率が良いのは確かです。
ただし現在のパソコン性能を考えれば、メモリ効率を理由にVariant型を避ける必要はほとんどありません。
明示的な型指定を否定しているわけではありませんが、Variant型はVBA上でバーチャルなセル空間を表現できる、とても便利なデータ型です。
実際今まで見てきたとおり、Excel VBAはVariant型の変数を使う前提で設計されている部分が多く、セルの値やセル範囲を扱う処理では、Variant型の利用が自然かつ効率的なのです![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()





