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 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.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 Function
ArrayList
クラス内のデータを文字列に変換するためのメソッドです。具体的には、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でクラスモジュールを利用できる様になると、プログラムの保全性や拡張性、再利用性が高まるので是非、学習し利用してみて下さい!!