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

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

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


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


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

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

ユーザー定義型

ユーザー定義型の基本については、以下を参照してください。
第110回.ユーザー定義型・構造体(Type)
ユーザー定義型は、名前の通りユーザーが定義できるデータ型になります。普通の変数は、1つの値しか入れられませんが、ユーザー定義型は、複数の異なるデータ型を入れる事が出来ます。プログラミング言語での一般的な呼び方としては、構造体とも呼ばれます。

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

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

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

クラスについて

クラスの基本については、以下を参照してください。
VBAのクラスとは(Class,Property)
VBAを覚えて、いろいろ作りながらネットで調べたりしていると、クラスとかオブジェクト指向といった言葉に出くわします。VBEの「挿入」の一番下にある「クラスモジュール」は気になっていたかもしれません。このクラスモジュールを使ってクラスを作ります。

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

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入門
同種のオブジェクトを複数まとめたものを「コレクション」と呼びます、コレクションもオブジェクトの一種です。例えば、Workbookオブジェクトが複数まとまったものは「Workbooksコレクション」Worksheetオブジェクトが複数まとまったものは「Worksheetsコレクション」オブジェクト名が単数形であるのに対し、

クラスは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について基本的な使い方を解説します。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 ・・・新着記事一覧を見る

import文(パッケージ・モジュールのインポート)|Python入門(9月24日)
例外処理(try文)とexception一覧|Python入門(9月23日)
リスト内包表記|Python入門(9月22日)
Pythonの引数は参照渡しだが・・・|Python入門(9月21日)
lambda(ラムダ式、無名関数)と三項演算子|Python入門(9月20日)
関数内関数(関数のネスト)とスコープ|Python入門(9月18日)
関数の定義(def文)と引数|Python入門(9月18日)
組み込み関数一覧|Python入門(9月17日)
辞書(dict型)|Python入門(9月16日)
入力規則への貼り付けを禁止する|VBA技術解説(9月16日)


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

1.最終行の取得(End,Rows.Count)|VBA入門
2.RangeとCellsの使い方|VBA入門
3.変数宣言のDimとデータ型|VBA入門
4.マクロって何?VBAって何?|VBA入門
5.セルのコピー&値の貼り付け(PasteSpecial)|VBA入門
6.Range以外の指定方法(Cells,Rows,Columns)|VBA入門
7.繰り返し処理(For Next)|VBA入門
8.セルに文字を入れるとは(Range,Value)|VBA入門
9.とにかく書いてみよう(Sub,End Sub)|VBA入門
10.マクロはどこに書くの(VBEの起動)|VBA入門




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


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



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