VBA技術解説
ユーザー定義型の制限とクラスとの使い分け

ExcelマクロVBAの問題点と解決策、VBAの技術的解説
公開日:2019-11-03 最終更新日:2020-03-27

ユーザー定義型の制限とクラスとの使い分け


VBAにはユーザー定義型(Type)があり、複数の要素(複数のデータ型)を含むデータ型を定義できます。
複数の値をひとまとめで扱う方法として配列がありますが、配列は同じ型の値しか扱うことができませんが、
ユーザー定義型の変数には、文字列型、数値型等々の複数のデータ型をひとまとめにして入れることができます。


使いこなすと非常に便利なユーザー定義型ですが、使い方にはいくつかの制限があります。
また、
ユーザー定義型の機能は、クラスにインスタンス変数(Public変数やPublicプロパティ)を作ることで同じことができます。

ここでは、ユーザー定義型とクラスを比較しつつ、ユーザー定義型の制限について説明します。

ユーザー定義型

ユーザー定義型の基本については、以下を参照してください。
第110回.ユーザー定義型・構造体(Type)
・Typeステートメントの構文 ・ユーザー定義型の使い方 ・ユーザー定義型の使用例 ・ユーザー定義型の制限 ・最後に

以降の説明で使うユーザー定義型は以下になります。

Public Type TypePerson
  番号 As Long
  名前 As String
  住所 As String
End Type

標準モジュールのモジュールレベルに記述します。

クラスについて

クラスの基本については、以下を参照してください。
VBAのクラスとは(Class,Property)
・オブジェクトとは ・オブジェクト指向とは ・カプセル化 ・オブジェクト指向とカプセル化とクラス ・クラスの必要性と利点 ・一般的なクラスに関する説明 ・クラスの比喩的説明 ・クラスの使い方 ・クラスを体験してみる ・クラスの使用例 ・クラス入門の最後に

以降の説明で使うクラスは以下になります。

Option Explicit

Public 番号 As Long
Public 名前 As String
Public 住所 As String

クラスのオブジェクト名:clsPerson

ユーザー定義型とクラスの速度比較

配列変数の型として、ユーザー定義型とクラスを使ったときの比較となります。
100万件の配列での比較をしています。
計測時間については、複数回実行の平均値ですが、
そもそも、PC環境によりますので、あくまで対比としての参考です。

ループ内で配列の要素に直接値を入れる
Sub type_sample1()
  Dim sTime As Double
  sTime = Timer
  Dim tPerson() As TypePerson
  ReDim tPerson(1 To 1000000)
  Dim i As Long
  For i = 1 To 1000000
    tPerson(i).番号 = i
    tPerson(i).名前 = "名前" & i
    tPerson(i).住所 = "住所" & i
  Next
  Debug.Print Timer - sTime
End Sub

Sub class_sample1()
  Dim sTime As Double
  sTime = Timer
  Dim cPerson() As New clsPerson
  ReDim cPerson(1 To 1000000)
  Dim i As Long
  For i = 1 To 1000000
    cPerson(i).番号 = i
    cPerson(i).名前 = "名前" & i
    cPerson(i).住所 = "住所" & i
  Next
  Debug.Print Timer - sTime
End Sub

ユーザー定義型:type_sample1
0.3875秒

クラス:class_sample1
2.0453秒

約5倍ほどの差があります。
クラスは、インスタンス作成の時間がかかるので仕方ありません。
とはいえ、100万回で2秒ですから、通常は処理時間として考慮するレベルのものではないと思います。

VBAクラスのインスタンス作成方法として、
上記VBAは、Forループ内で
Set cPerson() = New clsPerson
このようにしても、処理時間は変わりません。

※100万回のインスタンス生成について
さすがに通常はこれほどの回数を行う事は無いとは思います。
この記事を書くためにテストVBAを実行した範囲の事ですが、
クラスのVBAは実行後に何度かエクセルが反応なしになっています。
PC環境の問題もあると思いますが、VBAのメモリ解放に問題があるかもしれません。

関数の戻り値として、配列に1行分まとめて入れる
Sub type_sample2()
  Dim sTime As Double
  sTime = Timer
  Dim tPerson() As TypePerson
  ReDim tPerson(1 To 1000000)
  Dim i As Long
  For i = 1 To 1000000
    tPerson(i) = type_sub2(i)
  Next
  Debug.Print Timer - sTime
End Sub
Function type_sub2(i As Long) As TypePerson
  type_sub2.番号 = i
  type_sub2.名前 = "名前" & i
  type_sub2.住所 = "住所" & i
End Function

Sub class_sample2()
  Dim sTime As Double
  sTime = Timer
  Dim cPerson() As clsPerson
  ReDim cPerson(1 To 1000000)
  Dim i As Long
  For i = 1 To 1000000
    Set cPerson(i) = class_sub2(i)
  Next
  Debug.Print Timer - sTime
End Sub
Function class_sub2(i As Long) As clsPerson
  Set class_sub2 = New clsPerson
  class_sub2.番号 = i
  class_sub2.名前 = "名前" & i
  class_sub2.住所 = "住所" & i
End Function

ユーザー定義型:type_sample2
0.6734秒

クラス:class_sample2
2.1203秒

先の直接入れている場合にくらべると、ユーザー定義型の時間増加が目立ちます。
対して、クラスではほとんど時間の変化がありません。

これを詳細かつ正確に説明するのは難しいので大まかな説明になりますが、
ユーザー定義型は、データを値で渡していて(つまりデータをコピーしている)、
クラスはオブジェクトへの参照を渡している事に起因します。
クラスはオブジェクトとして、そのオブジェクトのアドレスを渡しているのでループ内で直接入れている場合とほとんど差が無いという事です。

関数の参照型引数として、配列に1行分まとめて入れる
Sub type_sample3()
  Dim sTime As Double
  sTime = Timer
  Dim tPerson() As TypePerson
  ReDim tPerson(1 To 1000000)
  Dim i As Long
  For i = 1 To 1000000
    Call type_sub3(tPerson(i), i)
  Next
  Debug.Print Timer - sTime
End Sub
Sub type_sub3(ByRef arg As TypePerson, i As Long)
  arg.番号 = i
  arg.名前 = "名前" & i
  arg.住所 = "住所" & i
End Sub

Sub class_sample3()
  Dim sTime As Double
  sTime = Timer
  Dim cPerson() As clsPerson
  ReDim cPerson(1 To 1000000)
  Dim i As Long
  For i = 1 To 1000000
    Call class_sub3(cPerson(i), i)
  Next
  Debug.Print Timer - sTime
End Sub
Sub class_sub3(arg As clsPerson, i As Long)
  Set arg = New clsPerson
  arg.番号 = i
  arg.名前 = "名前" & i
  arg.住所 = "住所" & i
End Sub

ユーザー定義型:type_sample3
0.4250秒

クラス:class_sample3
2.0703秒

ユーザー定義型もクラスも、どちらも最初のループ内で直接入れている場合とほとんど差がなくなっています。
どちらも引数で参照渡ししているので、アドレスを渡す事以外は差が無いという事です。

少なくともユーザー定義型を使う場合は、
type_sample2のような戻り値を使うのではなく、
type_sample3のようにByRefでの使い方をしたほうが良いでしょう。
ユーザー定義型(Type)を引数にする場合は、ByValではコンパイルエラーとなります。
この制限は、配列と同様になります。
ByRef指定にするか、指定を省略してください。

ユーザー定義型の制限

ユーザー定義型はVariant変数に入れることはできません

Sub type_sample4()
  Dim v
  Dim tPerson As TypePerson
  v = tPerson
End Sub

これはコンパイルエラーになります。

VBA マクロ ユーザー定義型 クラス

Variant変数にユーザー定義型を入れることはできません。
同じユーザー定義型変数同士であれば入れることはできます。

クラスはVariant変数に入れられます

Sub type_sample6()
  Dim dic
  Set dic = CreateObject("Scripting.Dictionary")
  Dim tPerson As TypePerson
  dic.Add "key", tPerson
End Sub

クラスなら問題ありません。
このような場合はクラスを使用してください。

ユーザー定義型はCollectionに入れることはできません

Sub type_sample5()
  Dim col As New Collection
  Dim tPerson As TypePerson
  col.Add tPerson
End Sub

これもコンパイルエラーになります。

第58回.コレクションとは(Collection):マクロVBA入門
・コレクションの中から単一オブジェクトを指定する場合 ・セルであるRangeオブジェクトのコレクションは? ・コレクションの要素数 ・Collectionオブジェクト

クラスはCollectionに入れられます


Sub class_sample5()
  Dim col As New Collection
  Dim cPerson As clsPerson
  col.Add cPerson
End Sub

クラスなら問題ありません。
このような場合はクラスを使用してください。

ユーザー定義型はDictionaryに入れることはできません

Sub type_sample6()
  Dim dic
  Set dic = CreateObject("Scripting.Dictionary")
  Dim tPerson As TypePerson
  dic.Add "key", tPerson
End Sub

これもコンパイルエラーになります。

Dictionary(ディクショナリー)連想配列の使い方について
・Dictionaryを使って重複を除く ・Dictionaryの使い方その2 ・Dictionaryの使い方その3 ・Dictionaryの使い方サンプル ・サイト内のDictionary関連記事

クラスはDictionaryに入れられます


Sub class_sample6()
  Dim dic
  Set dic = CreateObject("Scripting.Dictionary")
  Dim cPerson As New clsPerson
  dic.Add "key", cPerson
End Sub

クラスなら問題ありません。
このような場合はクラスを使用してください。

ユーザー定義型の制限とクラスとの使い分け

クラスを使うようになると、ユーザー定義型を使う必要性は低くなるかもしれません。
ただし、あくまでオブジェクトを作成することと、データ型を定義することの違いは理解しておきましょう。

クラスはインスタンス生成したオブジェクトの実態はただ一つです。
インスタンス作成したオブジェクト変数を他のオブジェクト変数に入れた場合
実体のオブジェクトは同じもので、オブジェクトがコピーされるわけではありません。
つまり、オブジェクトへの入り口をコピーしたにすぎません。
対して、ユーザー定義型はあくまでデータです。
ユーザー定義型の変数を他の同じユーザー定義型の変数に入れた場合はデータがコピーされます。

この違いはとても重要です。
ここを理解した上で、ユーザー定義型とクラスを使い分けることが最も重要です。
逆の見方をすれば、この違いを意識する必要がないのであれば、
ユーザー定義型の制限だけ意識していれば、都度どちらを使っても大差ないとも言えます。



同じテーマ「マクロVBA技術解説」の記事

VBAにおける配列やコレクションの起点について
VBAのマルチステートメント(複数のステートメントを同じ行に)
クリップボードに2次元配列を作成してシートに貼り付ける
ユーザー定義型の制限とクラスとの使い分け
シングルクォートの削除とコピー(PrefixCharacter)
空文字列の扱い方と処理速度について(""とvbNullString)
VBAにおける変数のメモリアドレスについて
Evaluateメソッド(文字列の数式を実行します)
Rangeオブジェクトの論理演算(差集合と排他的論理和)
VBAで写真の撮影日時や音楽動画の長さを取得する
VBAでWindowsMediaPlayerを使い動画再生する


新着記事NEW ・・・新着記事一覧を見る

VBA10大躓きポイント(初心者が躓きやすいポイント)|VBA技術解説(2024-03-05)
テンキーのスクリーンキーボード作成|ユーザーフォーム入門(2024-02-26)
無効な前方参照か、コンパイルされていない種類への参照です。|エクセル雑感(2024-02-17)
初級脱出10問パック|VBA練習問題(2024-01-24)
累計を求める数式あれこれ|エクセル関数応用(2024-01-22)
複数の文字列を検索して置換するSUBSTITUTE|エクセル入門(2024-01-03)
いくつかの数式の計算中にリソース不足になりました。|エクセル雑感(2023-12-28)
VBAでクリップボードへ文字列を送信・取得する3つの方法|VBA技術解説(2023-12-07)
難しい数式とは何か?|エクセル雑感(2023-12-07)
スピらない スピル数式 スピらせる|エクセル雑感(2023-12-06)


アクセスランキング ・・・ ランキング一覧を見る

1.最終行の取得(End,Rows.Count)|VBA入門
2.RangeとCellsの使い方|VBA入門
3.セルのコピー&値の貼り付け(PasteSpecial)|VBA入門
4.繰り返し処理(For Next)|VBA入門
5.変数宣言のDimとデータ型|VBA入門
6.ブックを閉じる・保存(Close,Save,SaveAs)|VBA入門
7.並べ替え(Sort)|VBA入門
8.条件分岐(IF)|VBA入門
9.セルのクリア(Clear,ClearContents)|VBA入門
10.マクロとは?VBAとは?VBAでできること|VBA入門




このサイトがお役に立ちましたら「シェア」「Bookmark」をお願いいたします。


記述には細心の注意をしたつもりですが、
間違いやご指摘がありましたら、「お問い合わせ」からお知らせいただけると幸いです。
掲載のVBAコードは動作を保証するものではなく、あくまでVBA学習のサンプルとして掲載しています。
掲載のVBAコードは自己責任でご使用ください。万一データ破損等の損害が発生しても責任は負いません。



このサイトがお役に立ちましたら「シェア」「Bookmark」をお願いいたします。
本文下部へ