[VBA]ArrayListの使い方を知れば動的配列も解決!

ArrayListの使い方VBA

みなさんは動的配列のように使えるArrayListオブジェクトをご存じだろうか。

VBAで配列を定義するときに考えることを想像してみると、まずどんな用途で使用するのか、配列のデータ型、その配列が静的か動的かどうか、このへんを固めていくことだろう。

用途として不特定数の要素を扱うとなれば動的配列を使うことになります。

  • Redimで要素数を再定義
  • RedimとPreserveで格納データを保持しながら再定義
  • LBoundとUBoundでループ処理
  • Eraseで配列の初期化

動的配列といえばこれらを意識しながら使用しますが、RedimとPreserveの繰り返しで動的に配列を定義していくのももちろんアリなのですが、もう少し簡単に、そして便利な使い方を目指すとなかなか面白いオブジェクトの存在が見つかるわけです。

それが「ArrayList」!!

VBAで動的配列を「簡単に、そして便利に」使いたい方向けにご紹介したいと思いますので、ご興味のある方はこのまま読み進めてください。

スポンサーリンク
スポンサーリンク

ArrayListとは

ArrayListとは、.Net Frameworkで使用できるクラスの一つです。

動的配列と言いながら、コレクションの一種と認識した方がいいでしょう。実際に完全修飾名で宣言すると「System.Collections.ArrayList」となるので、配列ベースのコレクションという位置づけでしょうか。

ArrayList クラス (System.Collections) | Microsoft Docs

VBAで.NET Frameworkのクラスを使用できることにも驚きですが、すべてのクラスをVBAで使用できるわけではないようで、この「ArrayList」クラスはVBA上で使用可能な限られたクラスの一つと言えます。

クラスということなので当然プロパティとメソッドがサポートされているため、VBAの配列と単純比較はできないが、あらゆる選択肢の一つとして覚えておいて損はないでしょう。

何よりVBAで動的配列を「簡単に、そして便利に」使いたい方にとってはとても都合のいいクラスでもあるため、その点にも着目しながらArrayListクラスをご紹介していきたいと思います。

ArrayListの宣言

VBA上でArrayListクラスを使用するためにはバインディングが必要になります。

Dim oList As Object

Set oList = CreateObject("System.Collections.ArrayList")

レイトバインディングの記述になりますが、これでインスタンスを生成することが出来ます。

前提として.NET Frameworkがインストールされている必要があるのですが、.NET FrameworkのバージョンによってVBAから呼び出せなくなる挙動もあるみたいです。

ArrayList バインディング エラー

CreateObjectの段階でオートメーションエラーになるようであれば「.Net Framework 3.5」を有効にすることで解消することが出来ます。

.Net Framework 3.5 を有効にする手順

手順1:コントロールパネルからプログラムをクリック。
.NET Framework 3.5 有効化手順①
手順2:Windows機能の有効化と無効化をクリック。
.NET Framework 3.5 有効化手順②
手順3:「.NET Framework 3.5」をクリックして「OK」ボタンをクリック。
.NET Framework 3.5 有効化手順③
手順4:「Windows Update でファイルを自動ダウンロードする」をクリック。
.NET Framework 3.5 有効化手順④
手順5:「.NET Framework 3.5」の有効化が完了しました。
.NET Framework 3.5 有効化 適用

参照設定(アーリーバインディング)の手順

VBA上でArrayListを参照設定してアーリーバインディングする場合、参照設定を設定するダイアログのリスト上には残念ながら含まれてきません。

ArrayListを使用可能にするためにはライブラリへのパスを直接指定して設定する必要があります。

ライブラリ名:Microsoft Common Language Runtime Class Library
指定するパス:C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.tlb

.NET Framework Microsoft Common Language Runtime Class Library

インスタンスの作成の具合は以下のとおりです。

Dim oList As mscorlib.ArrayList

Set oList = New mscorlib.ArrayList

アーリーバインディングで宣言しても自動クイックヒントなどの有益なサポートが効かないようなので、どちらかと言えばレイトバインディングであるCreateObjectでインスタンス作成した方がいいかもしれません。

※自動クイックヒント:「.」を押すとピロっと表示されるプロパティやメソッドなどの一覧

ArrayListのプロパティとメソッド

ではさっそく ArrayList のプロパティとメソッドについて説明していきます。

ArrayListは動的配列と違ってコレクションベースなのでオブジェクトという扱いになり、コレクション同様にプロパティやメソッドをサポートしているのが特徴と言えます。

そしてVBAのコレクションよりもArrayListの方がプロパティやメソッドが豊富で多機能です。

ここではVBAでも使用頻度の高そうなプロパティやメソッドを中心にご紹介します。

プロパティ

プロパティ説明
CountArrayListに格納された要素の数
ItemArrayList内の要素をインデックスで指定して値を取得・設定する

メソッド

メソッド説明
AddArrayListに要素を追加する ※最後尾に追加
ClearArrayList内の要素を全削除する
ContainsArrayList内に同じ値の要素が存在するか確認する
IndexOf_3ArrayList内をインデックス「0」から検索しインデックスを返します
InsertArrayListに要素を追加する ※インデックスで追加位置の指定が可能
LastIndexOfArrayList内をインデックス末尾から検索しインデックスを返します
RemoveArrayList内から要素を探し最初に見つかった要素を削除する
RemoveAtArrayList内の要素をインデックスで指定して削除する
ReverseArrayList内の要素を並びを反転する
SortArrayList内の要素を並べ替えます
ToArrayArrayList内の要素をバリアント型の一次元配列として出力する

ArrayListの基本的な使い方

Option Explicit

Sub ArrayList_how_to_use()
Dim objList As Object
Dim i As Long

    Set objList = CreateObject("System.Collections.ArrayList")
    
    'ArrayListにデータ追加
    Debug.Print "【ArrayListにデータ追加】"
    
    i = 0
    Do
        objList.Add "data_" & i
        i = i + 1
    Loop Until i >= 100000
    
    'ArrayListの格納件数を出力
    Debug.Print Space(3) & "格納件数:" & objList.Count
    Debug.Print "============================================================="
    
    '「data_XXX」が格納されているかチェック
    Debug.Print "【「data_XXX」が格納されているかチェック】"
    
    If Not objList.Contains("data_XXX") Then
        '格納されていなければインデックス「2」に挿入
        objList.Insert 2, "data_XXX"
        '要素「data_XXX」のインデックスを出力
        Debug.Print Space(3) & "要素「data_XXX」のインデックス:" & objList.IndexOf_3("data_XXX")
        Debug.Print "============================================================="
    End If
    
    Debug.Print "【要素「data_999」をインデックス「10」に挿入】"
    
    '要素「data_999」をインデックス「10」に挿入
    objList.Insert 10, "data_999"
    
    '要素「data_999」の検索しインデックスを出力
    Debug.Print Space(3) & "要素「data_999」のインデックス:" & objList.IndexOf_3("data_999")
    
    '要素「data_999」のインデックス末尾から検索しインデックスを出力
    Debug.Print Space(3) & "要素「data_999」のインデックス(末尾から検索):" & objList.LastIndexOf("data_999")
    Debug.Print "============================================================="
    
    'ArrayListのデータを並び替え
    Debug.Print "【ArrayListをソート】"
    objList.Sort
    
    '要素「data_999」の検索しインデックスを出力
    Debug.Print Space(3) & "要素「data_999」のインデックス:" & objList.IndexOf_3("data_999")
    
    '要素「data_999」のインデックス末尾から検索しインデックスを出力
    Debug.Print Space(3) & "要素「data_999」のインデックス(末尾から検索):" & objList.LastIndexOf("data_999")
    Debug.Print "============================================================="
    
    'ArrayListの要素を全削除
    Debug.Print "【ArrayListの要素を全削除】"
    objList.Clear
    
    'ArrayListの格納件数を出力
    Debug.Print Space(3) & "格納件数:" & objList.Count
    Debug.Print "============================================================="
    
    Set objList = Nothing
    
End Sub

サンプルの補足

上のArrayListを使ったサンプルでは、以下の処理を実施しております。

  1. データを10万ほどArrayListに追加
  2. 格納件数を出力し、10万件の件数確認
  3. 要素「data_XXX」の存在チェック
  4. 要素「data_XXX」が存在しなければインデックス「2」で挿入
  5. 要素「data_XXX」のインデックス検索
  6. 要素「data_999」が存在しなければインデックス「10」で挿入
  7. 要素「data_999」のインデックス検索(先頭から)
  8. 要素「data_999」のインデックス検索(末尾から)
  9. ArrayListを並び替え
  10. 要素「data_999」のインデックス検索(先頭から)
  11. 要素「data_999」のインデックス検索(末尾から)
  12. ArrayListの要素を全削除
  13. 格納件数を出力し、0件の件数確認

結果は以下の通りです。
arraylistの使い方

「Indexof」メソッドが「IndexOf_3」なわけ

vbaでArrayListクラスを使用するときに、「Indexof」のオーバーロードの解釈を「メソッド名_Num」で区別しているようです。
詳細はここでは割愛させて頂きますが、要素を指定してインデックス検索する場合は「IndexOf_3」としてメソッドをコールする必要があります。

ArrayListの使いどころ

ArrayListの使いどころですが、やっぱり動的配列の代替方法としてArrayListを使うっていうイメージですが、コレクションに検索機能や並べ替え(ソート)機能、配列出力機能などが加わっているので、頼もしいオブジェクトではあると思います。

ただし通常のコレクションの機能のみで事足りる場合には、VBAのコレクションを動的配列のように使用する使い方でもアリかもしれません。

値の編集などが用途で必要になってきた場合にVBAのコレクションでは対処できません。

また用途に検索や並べ替え(ソート)の用途がある場合には、VBAのコレクションではやっぱり対処できないですし、VBAの動的配列で対象する場合は、泥臭くロジックで作りこんでいく必要があります。

そういった手間をオブジェクトが提供する機能を活用することで作業ボリュームを軽減化していくのも開発作業の基本的なテクニックだと思います。

あとはVBAの一次元配列としての出力機能も備わっているため、ソート機能をArrayListクラスにゆだねて、結果は一次元配列で受け取り、後続の処理へつなげていくという使い方も有用です。

ArrayListを使ったソートテクニック

Option Explicit

Sub ArrayList_sort()
Dim objList As Object
Dim objDic As Object
Dim arrKey As Variant
Dim tmp As String
Dim v As Variant
Dim i As Long
    
    Set objDic = CreateObject("Scripting.Dictionary")
    Set objList = CreateObject("System.Collections.ArrayList")
    
    'Dictionaryにデータを格納(10件)
    i = 0
    Do
        Randomize
        tmp = "data_" & Int((100 - 0 + 1) * Rnd) + 0
        If Not objDic.Exists(tmp) Then
            objDic.Add tmp, vbNullString
            i = i + 1
        End If
    Loop Until i >= 10
    
    'Dictionaryのキーを出力しながら、ArrayListに格納
    For Each v In objDic.keys
        Debug.Print v
        objList.Add v
    Next
    
    Debug.Print "========================"
    
    'ArrayListを並べ替え(ソート)
    objList.Sort
    'ArrayListを一次元配列に出力
    arrKey = objList.Toarray
    
    '配列を順に出力
    For Each v In arrKey
        Debug.Print v
    Next
    
    objDic.RemoveAll
    objList.Clear

    Set objDic = Nothing
    Set objList = Nothing
    
End Sub

ソートサンプルの補足

VBAではDictionaryという連想配列があるのですが、キーとデータをセットで格納できるオブジェクトなのですが、ソート機能がサポートされていないので、何とかキーを並び替え(ソート)したいときなどに悩んでしまうわけです。

そのときにArrayListのソート機能を借りてキーの並び替え(ソート)を実施して、一次元配列で返してあげることで引き続きDictionary上での作業を行うことが出来るわけですが。

サンプルではキーのソートから一次元配列へ出力、そして並びの確認で終わっています。

ArrayListのソートサンプル

ご覧の通り、ArrayListオブジェクトのソート機能と一次元配列への出力機能は、発想次第で作成するロジックを強力にサポートすることが可能と言えます。

ご興味のある方はこの後のイメージを膨らませてみてください。

ArrayListと動的配列の違い

ArrayListはコレクションの一種ですので、配列ではないですが配列のように使うことが出来るので、一般的には以下のような切り分けで使用されている方も多いのではないかと思います。

  • 固定された要素数でデータを管理していく場合は配列
  • 動的に可変データを使用する用途ならArrayList(コレクション)

静的な配列はとても早いです。そして動的な配列の場合はケースバイケースです。

いずれにしてもRedimとPreserveが繰り返されるような事態はパフォーマンスや可読性を低下させてしまいます。

※配列の再定義が繰り返されない場合はこの限りではないです。

しかしArrayListなどのコレクションには追加メソッドや削除メソッドなど、明示的に何をしたいのか、何をさせたいのかをコード上で把握しやすく示すことが出来るのはメリットです。

またインデックスを指定したデータへのアクセスも可能でループ処理も容易です。

どちらが優劣という話ではないですが、用途への使いやすさ、コードの読み解きやすさはプログラムを作成・管理していく場合、とても重要な要素だと思います。

速度検証

ここではそれぞれの速度を検証してみたいと思います。

単純に10,000~10,000,000個のデータで処理の速度検証と傾向を分析することが目的です。

  • 静的配列
  • 動的配列 ※データ毎で再定義
  • コレクション
  • ArrayList

これらにデータボリュームを調整して格納する速度、2通りの参照方法(For ~ Next、For Each ~ Next)による処理速度を計測してArrayListの位置づけを考察できれば、と考えています。

Option Explicit

Sub SpeedCheck()

    Dim arrStatic(10000000) As String '検証データ数に応じて手動調整
    Dim arrDynamic() As String
    Dim objCol As New Collection
    Dim objList As Object   
    Dim DataRow As Long
    Dim StartTime As Double    
    Dim tmp As String
    Dim v As Variant
    Dim i As Long
    
    DataRow = 10000000                '検証データ数に応じて手動調整
    
    '********************
    '* 静的配列
    '********************
    
    StartTime = Timer
    
    Do
        arrStatic(i) = "data_" & i
        i = i + 1
    Loop Until i > DataRow
    
    Debug.Print "静的配列 - 格納:" & (Timer - StartTime) & "[秒]"
    
    StartTime = Timer
    
    For Each v In arrStatic
        tmp = v
    Next
    
    Debug.Print "静的配列 - 参照 - For Each:" & (Timer - StartTime) & "[秒]"
    
    StartTime = Timer
    
    For i = LBound(arrStatic) To UBound(arrStatic)
        tmp = arrStatic(i)
    Next

    Debug.Print "静的配列 - 参照 - For Next:" & (Timer - StartTime) & "[秒]"
    
    Debug.Print "=========================================="
    
    '********************
    '* 動的配列
    '********************
    
    i = 0
    StartTime = Timer

    Do
        ReDim Preserve arrDynamic(i)
        arrDynamic(i) = "data_" & i
        i = i + 1
    Loop Until i > DataRow

    Debug.Print "動的配列 - 格納:" & (Timer - StartTime) & "[秒]"
    
    StartTime = Timer
    
    For Each v In arrDynamic
        tmp = v
    Next

    Debug.Print "動的配列 - 参照 - For Each:" & (Timer - StartTime) & "[秒]"
    
    StartTime = Timer
    
    For i = LBound(arrDynamic) To UBound(arrDynamic)
        tmp = arrDynamic(i)
    Next

    Debug.Print "動的配列 - 参照 - For Next:" & (Timer - StartTime) & "[秒]"
    
    Debug.Print "=========================================="
    
    '********************
    '* コレクション
    '********************
    
    i = 0
    StartTime = Timer

    Do
        objCol.Add "data_" & i, CStr(i + 1)
        i = i + 1
    Loop Until i > DataRow

    Debug.Print "コレクション - 格納:" & (Timer - StartTime) & "[秒]"
    
    StartTime = Timer
    
    For Each v In objCol
        tmp = v
    Next
    
    Debug.Print "コレクション - 参照 - For Each:" & (Timer - StartTime) & "[秒]"
    
    StartTime = Timer
    
    For i = 1 To objCol.Count
        tmp = objCol(CStr(i))
    Next

    Debug.Print "コレクション - 参照 - For Next:" & (Timer - StartTime) & "[秒]"
    
    Debug.Print "=========================================="
    
    '********************
    '* ArrayList
    '********************
    
    i = 0
    StartTime = Timer

    Set objList = CreateObject("System.Collections.ArrayList")
    Do
        objList.Add "data_" & i
        i = i + 1
    Loop Until i > DataRow

    Debug.Print "ArrayList - 格納:" & (Timer - StartTime) & "[秒]"
    
    StartTime = Timer
    
    For Each v In objList
        tmp = v
    Next

    Debug.Print "ArrayList - 参照 - For Each:" & (Timer - StartTime) & "[秒]"
    
    StartTime = Timer
    
    For i = 0 To objList.Count - 1
        tmp = objList(i)
    Next

    Debug.Print "ArrayList - 参照 - For Next:" & (Timer - StartTime) & "[秒]"

End Sub

結果と考察

さて、結果を表にまとめてみました。以下の通りです。
arraylist 配列 動的配列 速度比較
処理の速度はPCのスペックに左右するので、参考程度に受け止めてください。

とりあえず表の補足ですが、すべて秒単位の結果になります。

コレクションのForループ処理のタイムが急激に悪化しまして、15分、30分待っても処理を終えないため断念しました。

さて、みなさんはどのようなに感じられますか?

10,000件くらいのデータ数であれば、ほぼ横並びの結果になったのは収穫ではないでしょうか。計測時間もデータ数に合わせて順当に増えているようですし。※コレクションのForループは除く

100,000件以降は徐々に差が目立ってきて、傾向に合わせた用途の使い分けが見えてきた気がします。

静的配列は文句なしです。使い方に適合する場合は積極的に使いましょう。

動的配列は再定義(Redim Preserve)の数だけ速度に影響を受けますが、静的には宣言できないが再定義が1回で済む場合は静的配列の速度と同等のパフォーマンスが得られます。※メモリ領域は別として

しかし処理が今回のようなデータ毎で再定義の繰り返しになるならば、データ数次第で他の手段も検討した方がいいでしょう。

コレクションはForループを使う用途でなければ動的配列の代替手段として使えます。格納とFor Eachループが高速なので、用途に合致する場合は全然使えます。

しかしコレクションは格納したデータを直接編集・更新することができません。必ず削除+追加でデータを差し替える必要があるため、その点の注意が必要です。

またコレクションはキーを任意で設定することが出来ますが、キー指定の参照は高速ですので、アイデア次第で活用の幅は広げられます。

さてArrayListですが、比較対象の中ではいまいち躍り出て活躍する場面はありませんでした。

ArrayListは.NET Framwork のコレクション系クラスですが、インデックス指定のForループでも結果は良好でした。

さらに言えばArrayListはコレクションでは叶わなかった格納した値の直接編集・更新に対応しているのは、用途の幅をかなり広げてくれる仕様といえます。

ArrayListの傾向としてデータ数が多すぎなければ良好なパフォーマンスが発揮できます。

さまざまなメソッド(容易な値の格納、値の直接編集・更新、値の検索、ソート(並び替え)、一次元配列出力など)がサポートされているので、その点を踏まえつつであれば動的配列の代替手段としてかなり役に立つのではないでしょうか。

ArrayListのまとめ

さて、興味が湧いて脇道にそれてしまいましたが、いかがだったでしょうか。

決して最速ではないオブジェクトながら、そこそこのパフォーマンスは出してくれる.NET Framwork のArrayListクラスです。

VBA上、最速ではないですが機能が充実している分、使用用途の幅が広いオブジェクトです。

最速を目指すならば用途にあった最適を選択すればいいのですが、手広く多機能なArrayListはメモリを酷使する無茶な用途でなければ知ってて損のないオブジェクトかと思います。

ご興味のある方は知識のストックにArrayListも加えてあげてください。

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