VBA

.NET FrameworkのArrayListクラスを使ったVBAの新しい可能性とArrayList自作

VBAから.NET Frameworkを利用することができるなんて、知っていましたか?これまでの常識を覆す驚きの可能性が広がっています。例えば、.NET Frameworkには「System.Collections名前空間のArrayListクラス」という強力なクラスが存在します。今回はこのArrayListクラスを使って、VBAから.NETの力強い機能を活かす方法に迫ってみましょう。

また、今回はArrayListクラスを自作する方法も紹介しています。この記事の内容を理解し使いこなす事が出来れば間違いなくVBA上級者だと言えます。

少し長い記事ですが最後までお付き合いください。

ArrayListクラス

まず初めに、ArrayListクラスについて簡単に紹介します。このクラスは、System.Collections名前空間に属しており、可変サイズの配列を扱うのに非常に便利です。具体的な使い方やメソッドの詳細については、

[MSDNのArrayListクラスページ](ArrayList クラス (System.Collections))をご覧ください。

実際のコーディング

それでは、ArrayListクラスを使った具体的なコーディング例を見ていきましょう。以下のコードは、.NET FrameworkのArrayListクラスを利用して、ランダムな数値を含む配列を作成し、ソートや複製、逆順などの操作を行います。

Sub UseArrayList()
    ' ArrayListクラスのインスタンスを作成
    Dim myList As Object
    Set myList = CreateObject("System.Collections.ArrayList")
    
    ' ランダムな数値を配列に格納
    Dim i As Integer
    For i = 1 To 10
        myList.Add Int((100 * Rnd) + 1)
    Next i
    
    ' 配列をソート
    myList.Sort
    
    ' 配列を逆順にする
    myList.Reverse
    
    ' 配列を複製
    Dim myCopy As Object
    Set myCopy = myList.Clone
    
    ' 結果の表示
    MsgBox "元の配列: " & Join(myList.ToArray, ", ") & vbCrLf & _
           "複製した配列: " & Join(myCopy.ToArray, ", ")
End Sub

このコードでは、ランダムな数値を含むArrayListを作成し、ソート、逆順、複製などの操作を行った後、結果をメッセージボックスで表示しています。

.NET FrameworkのArrayListクラスの問題

VBAでは.NETのSystem.Collections.ArrayListを使うことができますが、環境によって動かなかったりします。

それを解決するため、クラスでArrayListを実装していく方法を説明します。
独自のクラスとして実装することで、環境に依存せず使えるようになるのでとても便利です。

ArrayListクラスの実装

ArrayListのような動的なリストをVBAで実装するには、クラスモジュールを使用して自作のクラスを作成します。以下に、基本的なArrayListを模倣するVBAクラスモジュールの例を示します。

ArrayList クラスモジュール (ArrayList.cls) を作成

' ArrayList.cls

Private values As Collection

Public Sub Initialize()
    Set values = New Collection
End Sub

Public Sub Add(item As Variant)
    values.Add item
End Sub

Public Function Get(index As Integer) As Variant
    If index >= 1 And index <= values.Count Then
        Get = values(index)
    Else
        Err.Raise 9, "ArrayList", "Index out of bounds"
    End If
End Function

Public Function Count() As Integer
    Count = values.Count
End Function

利用例

Sub TestArrayList()
    Dim myList As New ArrayList
    
    ' データの追加
    myList.Add "John"
    myList.Add 25
    myList.Add "Alice"
    myList.Add 30
    myList.Add "Bob"
    myList.Add 22
    
    ' データの表示
    DisplayArrayList myList
End Sub

Sub DisplayArrayList(arrList As ArrayList)
    Dim i As Integer
    For i = 1 To arrList.Count
        Debug.Print arrList.Get(i)
    Next i
End Sub

このコードは、ArrayListクラスを使用してデータを格納し、その後表示するものです。ArrayListクラス内でCollectionオブジェクトを使用してデータを格納しています。InitializeメソッドでCollectionを初期化し、Addメソッドでデータを追加します。Getメソッドは指定したインデックスの要素を取得し、Countメソッドは要素の数を返します。

これで、VBAのArrayList風のクラスが作成されました。必要に応じてこのクラスを拡張し、他のメソッドや機能を追加できます。

ArrayList クラスの拡張

折角自作したので、このクラスを本家.NetFrameworkに負けないくらいに拡張してみましょう!

2次元配列に対応

' ArrayList.cls

Private values As Collection

Public Sub Initialize()
    Set values = New Collection
End Sub

Public Sub Add(item As Variant)
    values.Add item
End Sub

Public Function Get(index As Integer) As Variant
    If index >= 1 And index <= values.Count Then
        Get = values(index)
    Else
        Err.Raise 9, "ArrayList", "Index out of bounds"
    End If
End Function

Public Function Count() As Integer
    Count = values.Count
End Function

Public Sub Clear()
    Set values = New Collection
End Sub

Public Function ToArray() As Variant
    Dim result() As Variant
    ReDim result(1 To values.Count)
    
    Dim i As Integer
    For i = 1 To values.Count
        result(i) = values(i)
    Next i
    
    ToArray = result
End Function

Public Function To2DArray() As Variant
    Dim result() As Variant
    ReDim result(1 To values.Count, 1 To 1)
    
    Dim i As Integer
    For i = 1 To values.Count
        result(i, 1) = values(i)
    Next i
    
    To2DArray = result
End Function

利用例

Sub TestArrayList()
    Dim myList As New ArrayList
    
    ' データの追加
    myList.Add "John"
    myList.Add 25
    myList.Add "Alice"
    myList.Add 30
    myList.Add "Bob"
    myList.Add 22
    
    ' データの表示
    DisplayArrayList myList
    
    ' 2次元配列に変換して表示
    Display2DArray myList.To2DArray
End Sub

Sub DisplayArrayList(arrList As ArrayList)
    Dim i As Integer
    For i = 1 To arrList.Count
        Debug.Print arrList.Get(i)
    Next i
End Sub

Sub Display2DArray(arr As Variant)
    Dim i As Integer, j As Integer
    For i = LBound(arr, 1) To UBound(arr, 1)
        For j = LBound(arr, 2) To UBound(arr, 2)
            Debug.Print arr(i, j)
        Next j
    Next i
End Sub

更新されたArrayListクラスには、Clearメソッドでリストをクリアする機能、ToArrayメソッドで1次元配列に変換する機能、To2DArrayメソッドで2次元配列に変換する機能が追加されました。これにより、より柔軟で多様なデータ操作が可能になります。

ArrayList クラスを更に拡張

ArrayListを更に強化するために追加できる機能や改善点を考えると以下の様なものがあります。

  1. 挿入・削除のサポート:
    • Insertメソッド: 指定した位置に要素を挿入する。
    • Removeメソッド: 指定した要素を削除する。
  2. 検索機能の向上:
    • IndexOfメソッド: 指定した要素のインデックスを返す。
    • Containsメソッド: リストが指定した要素を含んでいるかどうかを返す。
  3. 範囲指定:
    • GetRangeメソッド: 指定した範囲の要素を新しいArrayListとして取得する。
  4. ソート機能:
    • Sortメソッド: 要素を昇順または降順でソートする。
  5. 反復処理サポート:
    • ForEachメソッド: 要素を反復処理する。
  6. コピー機能:
    • Cloneメソッド: 現在のArrayListをコピーして新しいArrayListを作成する。
  7. ToStringメソッド:
    • ToStringメソッド: ArrayListを文字列として表現する。
  8. イベント処理:
    • 要素が追加・削除されたときなどのイベントをサポートする。
  9. データ型の制約:
    • 特定のデータ型の要素のみを受け入れるような制約を導入する。
  10. エラーハンドリングの向上:
    • エラーハンドリングをもっと柔軟にし、より具体的なエラーメッセージを提供する。
  11. map, filter, flattenの実装
  12. ワークシートへの読み書きに対応

これらの機能を組み合わせることで、より多くのシナリオに対応できるより強力なArrayListクラスを作成できます。追加の機能は、利用する状況や必要性によって変わるため、私があると便利だと思う機能を列挙しました。

折角なので簡単にこれらの実装を紹介します!
全てArrayList.clsに追加して下さい。

挿入・削除のサポート

Public Sub Insert(index As Integer, item As Variant)
    If index >= 1 And index <= values.Count + 1 Then
        values.Add item, , index
    Else
        Err.Raise 9, "ArrayList", "Index out of bounds"
    End If
End Sub

Public Sub Remove(index As Integer)
    If index >= 1 And index <= values.Count Then
        values.Remove index
    Else
        Err.Raise 9, "ArrayList", "Index out of bounds"
    End If
End Sub

Insertメソッド

  • index パラメータは挿入する位置のインデックスです。1から Count + 1 までの範囲内である必要があります。
  • item パラメータは挿入する要素です。
  • values.Add item, , index で、指定されたインデックスに要素を挿入します。
  • インデックスが範囲外の場合、エラー番号9(”Index out of bounds”)を発生させます。

Removeメソッド

  • index パラメータは削除する要素のインデックスです。1から Count までの範囲内である必要があります。
  • values.Remove index で、指定されたインデックスの要素を削除します。
  • インデックスが範囲外の場合、エラー番号9(”Index out of bounds”)を発生させます。

検索機能の向上

Public Function IndexOf(item As Variant) As Integer
    On Error Resume Next
    IndexOf = values.Item(item)
    On Error GoTo 0
End Function

Public Function Contains(item As Variant) As Boolean
    Contains = (IndexOf(item) > 0)
End Function

指定されたアイテムが ArrayList 内で最初に出現する位置(インデックス)を検索する IndexOf メソッドと、指定されたアイテムが ArrayList に含まれているかどうかを確認する Contains メソッドを定義しています。

  • IndexOf メソッドは、values コレクション内で指定されたアイテムを検索します。
  • On Error Resume Next は、エラーが発生しても次の行に進むように設定します。これにより、アイテムが見つからない場合にエラーが発生してもコードが停止しないようになります。
  • IndexOf メソッドは、アイテムが見つかった場合はそのインデックスを、見つからなかった場合は 0 を返します。
  • Contains メソッドは、IndexOf メソッドを使用してアイテムが ArrayList 内で見つかるかどうかを確認します。
  • Contains メソッドは、アイテムが見つかれば True、見つからなければ False を返します。

これにより、IndexOf メソッドを使用してアイテムがどこにあるかを調べ、Contains メソッドを使用してアイテムが含まれているかどうかを簡単に確認できます。

範囲指定

Public Function GetRange(startIndex As Integer, count As Integer) As ArrayList
    If startIndex >= 1 And startIndex <= values.Count Then
        Dim result As New ArrayList
        Dim i As Integer
        For i = startIndex To startIndex + count - 1
            result.Add values(i)
        Next i
        Set GetRange = result
    Else
        Err.Raise 9, "ArrayList", "Invalid start index"
    End If
End Function

このGetRange関数は、指定された開始位置(startIndex)から始まり、指定された数(count)だけの要素を含む新しいArrayListオブジェクトを作成します。

元のArrayListオブジェクト(values)から指定された範囲を取り出し、それを含む新しいArrayListオブジェクトを作成しています。これにより、データの一部を抽出し、新しいArrayListとして扱うことができます。

ソート機能

Public Sub Sort(Optional descending As Boolean = False)
    Dim tempArray() As Variant
    tempArray = ToArray()
    If descending Then
        Call VBA.Sort(tempArray, Array:=Array(1, 2), Order:=xlDescending)
    Else
        Call VBA.Sort(tempArray, Array:=Array(1, 2))
    End If
    Clear
    Dim i As Integer
    For i = LBound(tempArray) To UBound(tempArray)
        Add tempArray(i, 1)
    Next i
End Sub

このコードの主な機能は次の通りです:

  • ToArray メソッドを使って ArrayList の要素を2次元のVariant配列に変換します。
  • VBA.Sort を使って、配列を指定した列で昇順または降順にソートします。この際、Array(1, 2) と指定することで、1列目を基準にソートされます。
  • ソート前の ArrayList の要素を Clear メソッドでクリアします。
  • ソートされた2次元配列から各要素を取り出し、Add メソッドを使って再び ArrayList に追加します。

このメソッドを使用することで、ArrayList 内の要素を指定した列でソートできます。descending パラメーターに True を指定すれば、降順にソートされます。指定しない場合は昇順にソートされます。

反復処理サポート

Public Sub ForEach(callback As String)
    Dim i As Integer
    For i = 1 To values.Count
        Application.Run callback, values(i)
    Next i
End Sub

この機能では、指定されたコールバック関数を使用して要素を反復処理します。

ArrayList クラスにおいて、各要素に対して特定の処理を適用するための ForEach メソッドを実現しています。

  • Public Sub ForEach(callback As String) は、ForEach メソッドを公開し、callback という文字列型の引数を受け取ります。この callback は、各要素に対して適用されるコールバック関数の名前を指定します。
  • Dim i As Integer は、For ループ内で使用する整数型の変数 i を宣言します。この変数は 1 から values.Count までの範囲を取ります。
  • For i = 1 To values.Count は、1 から values.Count までの範囲でループを回します。values.CountArrayList 内の要素の数を表します。
  • Application.Run callback, values(i) は、Application.Run メソッドを使用して、指定されたコールバック関数 callback を呼び出します。values(i) は、ArrayList 内の現在の要素を表します。この要素がコールバック関数に渡され、処理が行われます。
  • Next i は、For ループを終了し、次の要素に対して同様の処理を行います。

この ForEach メソッドを使用すると、ArrayList 内の各要素に対して、特定の処理を一括で適用できます。例えば、各要素を画面に表示したり、特定の処理を行ったりする際に便利です。

コピー機能

Public Function Clone() As ArrayList
    Dim clonedList As New ArrayList
    Dim i As Integer
    For i = 1 To values.Count
        clonedList.Add values(i)
    Next i
    Set Clone = clonedList
End Function

このメソッドは、ArrayList の複製を作成します。

このメソッドは次の手順で動作します:

  1. Clone メソッドが呼び出されると、新しい ArrayList オブジェクト clonedList が作成されます。
  2. オリジナルの ArrayList の要素が1つずつ clonedList に追加されます。これにより、新しい ArrayList が元のリストのコピーを持つことになります。
  3. 最後に、複製された ArrayList オブジェクトが呼び出し元に返されます。

このメソッドを使用することで、元の ArrayList を変更することなく、その内容を保持した別の ArrayList を作成できます。

ToStringメソッド

Public Function ToString() As String
    Dim result As String
    Dim i As Integer
    For i = 1 To values.Count
        result = result & values(i) & ", "
    Next i
    If Len(result) > 0 Then
        result = Left(result, Len(result) - 2)
    End If
    ToString = "[" & result & "]"
End Function

ArrayListクラス内のデータを文字列に変換するためのメソッドです。具体的には、ArrayList内の要素をコンマで区切り、それを角かっこで括って文字列として表現します。

この関数が具体的に何をしているかを解説します:

  1. result変数は最終的な文字列を格納するための変数です。
  2. Forループを使用して、ArrayList内の各要素をコンマで区切りながらresultに連結していきます。
  3. 最後に、文字列の最後に付いている余分なコンマとスペースを削除します。
  4. 最後に、括弧でresultを囲み、最終的な文字列として関数が返します。

これにより、ArrayList内のデータを人間が理解しやすい文字列に変換できます。

イベント処理

  • イベント処理をサポートするためのフレームワークが必要です。
  • イベント処理はクラス モジュールと UserForm で使えます。
  • 例えば、クラス モジュールで CollectionChange イベントを宣言し、 ‘Add や Remove メソッド内でこのイベントを発火させることができます。

データ型の制約

  • データ型を指定できるようにすることで、特定のデータ型の要素のみを受け入れるようにできます。
  • ただし、VBAは動的なジェネリクス型をサポートしていないため、 どの型でも受け入れるように実装しておくことが一般的です。

エラーハンドリングの向上

  • エラーハンドリングをもっと柔軟にし、より具体的なエラーメッセージを提供することができます。
  • 例えば、エラーメッセージに変数名やメソッド名を含めることでデバッグがしやすくなります。

map メソッド

Public Function Map(callback As String) As ArrayList
    Dim result As New ArrayList
    Dim i As Integer
    For i = 1 To values.Count
        result.Add Application.Run(callback, values(i))
    Next i
    Set Map = result
End Function

この map メソッドは、指定されたコールバック関数を使用して要素を変換し、新しい ArrayList を返します。

filter メソッド

Public Function Filter(callback As String) As ArrayList
    Dim result As New ArrayList
    Dim i As Integer
    For i = 1 To values.Count
        If Application.Run(callback, values(i)) Then
            result.Add values(i)
        End If
    Next i
    Set Filter = result
End Function

この filter メソッドは、指定されたコールバック関数を使用して要素をフィルタリングし、新しい ArrayList を返します。

flatten メソッド

Public Function Flatten() As ArrayList
    Dim result As New ArrayList
    Dim i As Integer
    For i = 1 To values.Count
        If TypeName(values(i)) = "ArrayList" Then
            Dim subList As ArrayList
            Set subList = values(i)
            Dim j As Integer
            For j = 1 To subList.Count
                result.Add subList.Get(j)
            Next j
        Else
            result.Add values(i)
        End If
    Next i
    Set Flatten = result
End Function

この flatten メソッドは、2次元の ArrayList を1次元に平坦化します。各要素が ArrayList であればその中身も展開します。

ワークシートへの書き込み

Public Sub WriteToWorksheet(targetWorksheet As Worksheet, Optional startCell As Range)
    Dim targetRange As Range
    If startCell Is Nothing Then
        Set targetRange = targetWorksheet.Cells(targetWorksheet.Rows.Count, 1).End(xlUp).Offset(1, 0)
    Else
        Set targetRange = startCell
    End If

    Dim i As Integer
    For i = 1 To values.Count
        targetRange.Offset(i - 1, 0).Value = values(i)
    Next i
End Sub

このメソッドは、ArrayList の要素を指定されたワークシートに書き込みます。開始セルを指定しない場合、対象ワークシートの最終行の下に書き込まれます。

ワークシートから読み込み

Public Sub ReadFromWorksheet(sourceWorksheet As Worksheet, sourceRange As Range)
    Dim sourceData As Variant
    sourceData = sourceRange.Value

    Dim i As Integer
    For i = LBound(sourceData, 1) To UBound(sourceData, 1)
        Add sourceData(i, 1)
    Next i
End Sub

このメソッドは、指定されたワークシートと範囲からデータを読み込んで ArrayList に追加します。

まとめ

VBAには、JavaやC#などで使われているArrayListのような動的なリスト構造が組み込まれていません。しかし、今回作成したArrayListクラスを利用する事でそれらの言語に引けを取らない処理が可能になります。

VBA自体は言語として貧弱な面も多いのですが、様々な工夫をする事で他の言語では実装が面倒な処理を実現できます。

私のブログは、クラスモジュールを多用するので少し難しく感じるかもしれませんが、VBAでクラスモジュールを利用できる様になると、プログラムの保全性や拡張性、再利用性が高まるので是非、学習し利用してみて下さい!!