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

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

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


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


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

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

ユーザー定義型

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

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

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

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

クラスについて

クラスの基本については、以下を参照してください。
VBAのクラスとは(Class,Property)
VBAを覚えて、いろいろ作りながらネットで調べたりしていると、クラスやらオブジェクト指向やらという言葉に出くわします。いくら言葉を尽くしても、これらクラスやオブジェクト指向を完全に説明しつくすことは難しいと思われます。オブジェクトとは 操作対象の事ですと説明されたりしますが、まずは何かの物体、つまりは対象物と理解すれば良いでしょう。

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



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オブジェクトについて簡単な使用例を上げて解説して欲しいです。検討お願いしますm(_ _)m と頂いたので、分かる範囲内で解説します。実際は、私はあまり使う事はありません。

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


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

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

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

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

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

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



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

Applicationを省略できるApplicationのメソッド・プロパティ一覧
PowerQueryの強力な機能をVBAから利用する方法
ShapesとDrawingObjectsの相違点と使い方
新規挿入可能なシート名の判定
VBAにおける配列やコレクションの起点について
VBAのマルチステートメント(複数のステートメントを同じ行に)
クリップボードに2次元配列を作成してシートに貼り付ける
ユーザー定義型の制限とクラスとの使い分け
シングルクォートの削除とコピー(PrefixCharacter)
空文字列の扱い方と処理速度について(""とvbNullString)
VBAにおける変数のメモリアドレスについて


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

VBAにおける変数のメモリアドレスについて|VBA技術解説(11月8日)
空文字列の扱い方と処理速度について(""とvbNullString)|VBA技術解説(1月7日)
Errオブジェクトとユーザー定義エラー|VBA入門(11月5日)
シングルクォートの削除とコピー(PrefixCharacter)|VBA技術解説(11月4日)
ユーザー定義型の制限とクラスとの使い分け|VBA技術解説(11月3日)
クリップボードに2次元配列を作成してシートに貼り付ける|VBA技術解説(11月1日)
VBAクラスを使ったイベント作成(Event,RaiseEvent,WithEvents)|VBA技術解説(10月31日)
VBAクラスのAttributeについて(既定メンバーとFor Each)|VBA技術解説(10月19日)
VBAの用語について:ステートメントとは|VBA技術解説(10月16日)
VBAのマルチステートメント(複数のステートメントを同じ行に)|VBA技術解説(10月14日)


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

1.最終行の取得(End,Rows.Count)|VBA入門
2.セルのコピー&値の貼り付け(PasteSpecial)|VBA入門
3.RangeとCellsの使い方|ExcelマクロVBA入門
4.Range以外の指定方法(Cells,Rows,Columns)|VBA入門
5.変数宣言のDimとデータ型|ExcelマクロVBA入門
6.繰り返し処理(For Next)|ExcelマクロVBA入門
7.マクロって何?VBAって何?|ExcelマクロVBA入門
8.ひらがな⇔カタカナの変換|エクセル基本操作
9.空白セルを正しく判定する方法(IsEmpty,IsError,HasFormula)|VBA技術解説
10.セルに文字を入れるとは(Range,Value)|VBA入門



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

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


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




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