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を更に強化するために追加できる機能や改善点を考えると以下の様なものがあります。
- 挿入・削除のサポート:
Insertメソッド: 指定した位置に要素を挿入する。Removeメソッド: 指定した要素を削除する。
- 検索機能の向上:
IndexOfメソッド: 指定した要素のインデックスを返す。Containsメソッド: リストが指定した要素を含んでいるかどうかを返す。
- 範囲指定:
GetRangeメソッド: 指定した範囲の要素を新しいArrayListとして取得する。
- ソート機能:
Sortメソッド: 要素を昇順または降順でソートする。
- 反復処理サポート:
ForEachメソッド: 要素を反復処理する。
- コピー機能:
Cloneメソッド: 現在のArrayListをコピーして新しいArrayListを作成する。
- ToStringメソッド:
ToStringメソッド: ArrayListを文字列として表現する。
- イベント処理:
- 要素が追加・削除されたときなどのイベントをサポートする。
- データ型の制約:
- 特定のデータ型の要素のみを受け入れるような制約を導入する。
- エラーハンドリングの向上:
- エラーハンドリングをもっと柔軟にし、より具体的なエラーメッセージを提供する。
- map, filter, flattenの実装
- ワークシートへの読み書きに対応
これらの機能を組み合わせることで、より多くのシナリオに対応できるより強力な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 SubInsertメソッド
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.CountはArrayList内の要素の数を表します。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 の複製を作成します。
このメソッドは次の手順で動作します:
Cloneメソッドが呼び出されると、新しいArrayListオブジェクトclonedListが作成されます。- オリジナルの
ArrayListの要素が1つずつclonedListに追加されます。これにより、新しいArrayListが元のリストのコピーを持つことになります。 - 最後に、複製された
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 FunctionArrayListクラス内のデータを文字列に変換するためのメソッドです。具体的には、ArrayList内の要素をコンマで区切り、それを角かっこで括って文字列として表現します。
この関数が具体的に何をしているかを解説します:
result変数は最終的な文字列を格納するための変数です。Forループを使用して、ArrayList内の各要素をコンマで区切りながらresultに連結していきます。- 最後に、文字列の最後に付いている余分なコンマとスペースを削除します。
- 最後に、括弧で
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でクラスモジュールを利用できる様になると、プログラムの保全性や拡張性、再利用性が高まるので是非、学習し利用してみて下さい!!
