TL;DR —
Type(ユーザー定義型、UDT)を使うと、ある形に一度だけ名前を付け、 そのフィールドすべてを 1 つの変数で持ち運べます。インデックスを揃え続けなければ ならない 3 本の配列をやりくりする代わりに、1 本のレコード配列が手に入ります。Typeは値型です。b = aという代入はすべてのフィールドをコピーするので、 2 つの変数は独立します — 共有されるクラスのインスタンスとは正反対です。宣言は 標準モジュールの一番上に置き、レコードに何かをさせたくなったときだけ クラスモジュールに手を伸ばしましょう。
' 標準モジュールの一番上、Sub や Function より前に置く:
Type Employee
Name As String
Age As Long
Salary As Currency
End Type
Sub Demo()
Dim e As Employee ' 1 つの変数が 3 つのフィールドすべてを保持する
e.Name = "Sarah"
e.Age = 34
e.Salary = 65000
Dim copy As Employee
copy = e ' 値のコピー — すべてのフィールドが複製される
copy.Salary = 99000 ' copy を変えても e には影響しない
MsgBox e.Salary ' 依然として 65000
End Sub
「1 つの対象について複数の情報を追いかける」VBA コードの多くは、平行配列に行き着き
ます — names()、ages()、salaries() のように、すべて同じ順序を保たなければ
ならない配列です。1 つを並べ替えて他を忘れれば、5 行目の名前が 12 行目の給与に
結びついてしまいます。しかも警告するエラーは一切出ません。Type はこれを根本から
直します。フィールドはもう互いに離れてバラバラになれません。1 つの変数の中に一緒に
住んでいるからです。
この記事で学べること
- 考え方の軸 — 1 行を 1 つの変数として扱う
- Type をどこで宣言しなければならないか(そして Sub の内側ではなぜ失敗するのか)
- 意外に思われるルール — Type はコピーする、共有はしない
- 本当の利点 — 平行配列ではなく、1 本のレコード配列
- 越えられない限界 — Type は振る舞いを持てず、Collection にも入れられない
- Type vs クラスモジュール — どう選ぶか
考え方の軸:Type は「1 行を 1 つの変数にまとめたもの」
Type は、テーブルの 1 行を変数に昇格させたものだと考えてください。従業員の
ワークシート行には、名前・年齢・給与が横に並んでいます。Type は、それと同じ
まとまりをコードの中で与えてくれます。列を一度だけ定義すれば(Type Employee ... End Type)、あとは Dim e As Employee のたびに、同じスロットを持つ真新しい 1 行が
手に入ります。
これが要点のすべてです。本来ひとまとまりの、バラバラの値を渡すのをやめて、
代わりにラベルを貼った 1 つのものを渡す、ということです。従業員を必要とする関数は、
正しい順序で並べ続けなければならない 3 つのパラメーターではなく、たった 1 つの
Employee 引数を受け取ります。
Type はどこで宣言しなければならないか
Type の宣言はモジュールレベル — 標準モジュールの一番上、あらゆる手続きより前に
置きます。Sub の内側に入れると、プロジェクトはコンパイルできません。これは、
Type を Dim と同じように扱う人がはまるところです。Dim は手続きの内側で実行する
ステートメントですが、Type はモジュールやプロジェクト全体に対して形を定義する
宣言だからです。
覚えておく価値のある配置ルールが 2 つあります。
Public Type(モジュールレベルでの既定)は、その型をプロジェクト全体で使える ようにします。 1 つのモジュールに閉じ込めたいときはPrivate Typeを使います。Public Typeをクラスモジュールの内側で宣言することはできません。 VBA は 「プライベートなオブジェクト モジュール内では、パブリックなユーザー定義型を定義 できません」として拒否します。共有するTypeは、クラスやシートのコードではなく、 通常のモジュール(Module1、または専用のTypesモジュール)に置いてください。
意外に思われるルール:Type はコピーする、共有はしない
これは Type について理解すべき唯一にして最も重要なことです。というのも、まさに
ここがオブジェクトと異なる分かれ目だからです。Type は値型です。copy = e と
書くと、VBA はすべてのフィールドを真新しい独立した変数に複製します。
Dim a As Employee, b As Employee
a.Salary = 50000
b = a ' 全フィールドの完全なコピー
b.Salary = 70000 ' b だけを書き換える
Debug.Print a.Salary ' 50000 — a は手つかず
これをクラスのインスタンスと比べてみてください。そちらは
Set b = a が a と b を同じオブジェクトの 2 つの名前にするので、一方を
編集すれば両方が変わります。どこかに代入したあとオブジェクトが「ひとりでに」変わって
痛い目に遭った経験があるなら、Type の代入時コピーという振る舞いはその解毒剤です —
そして多くの場合、まさにあなたが望むものです。
コピーが起きない唯一の場所は、手続きへの受け渡しです。Type の引数は既定で
ByRef なので、Sub があなたの元のレコードを変更できます。
Sub GiveRaise(emp As Employee) ' 既定で ByRef
emp.Salary = emp.Salary * 1.1 ' 呼び出し元のレコードを書き換える
End Sub
呼び出された側にプライベートなコピーで作業してほしいなら、ByVal で渡します —
通常の変数を支配するのと同じ ByRef vs ByVal のルールです。
本当の利点:1 本のレコード配列
Type が真価を発揮するのがここです。手作業で揃え続けなければならない N 本の
平行配列の代わりに、1 本のレコード配列を宣言します。
Dim staff(1 To 100) As Employee
staff(1).Name = "Sarah"
staff(1).Salary = 65000
staff(2).Name = "Tom"
staff(2).Salary = 58000
' staff() を並べ替え・フィルター・順序変更しても、すべてのフィールドが一緒に動く —
' 名前と給与が二度とずれることはない。
これが取り除く失敗のかたちは、静かで厄介です。names()、ages()、salaries() を
別々の配列にしていると、そのうち 1 つを並べ替える操作 —
ソート、挿入、削除 — は、すべてにまったく同じように適用するのを覚えていない限り、
残りをずらしてしまいます。Employee の配列なら、同期を保つべきものは何もありません。
フィールドが互いのそばを一度も離れていないからです。
越えられない限界:振る舞いも Collection も持てない
Type は意図的に「おバカ」です — データを保持するだけで、それ以外は何もしません。
そのために越えられない限界が 2 つあり、それが「Type では手に負えなくなった」合図を
教えてくれます。
- メソッドを持てません。 レコードが何かをする必要が出た瞬間 — 自分の年齢を
検証する、自分を文字列に整形する、合計を再計算する —
Typeは力になれません。 振る舞いはクラスモジュールのものです。 CollectionにもDictionaryにも入れられません。 これらのストアはVariant値を保持しますが、UDT はVariantに変換できません。Typeでcoll.Add eを 試すと、VBA は拒否します。増やせてキー付きのレコードストアが必要なら、それだけでも クラスに切り替える理由になります。クラスのインスタンスは、Collectionが受け入れる オブジェクトだからです。
Type vs クラスモジュール:どう選ぶか
Type(UDT) |
クラスモジュール | |
|---|---|---|
| 保持するもの | データのみ | データと振る舞い |
| 分類 | 値型 | 参照型(オブジェクト) |
b = a |
全フィールドをコピー | Set b = a は 1 つのインスタンスを共有 |
| 宣言する場所 | 標準モジュール、手続きより前 | 専用のクラスモジュール |
Collection に格納できる? |
いいえ | はい |
| 作成方法 | Dim e As Employee |
Set e = New clsEmployee |
| 向いている場面 | フィールドをただ一緒に運びたいとき | レコードにメソッドが必要、またはコレクションに入れる必要があるとき |
私の経験則はこうです。 フィールドがただ一緒にいてくれればよく、安価で独立した
コピーが欲しいなら、Type が正しい道具です — ここでクラスに手を伸ばすのは過剰設計
です。レコードに振る舞いが必要になった瞬間、あるいはそれらの多くを Collection に
保持する必要が出た瞬間に、クラスへ切り替えます。その中間はすべて判断次第で、「まず
Type で始めて、あとで昇格させる」というのは十分に良い既定方針です。
ExcelMaster の活用
平行配列と Type と本格的なクラスの間で選ぶこと — そして Type はコピーし、
オブジェクトは共有する、と覚えておくこと — は、まさに微妙に間違えやすいモデリングの
選択です。それが生むバグ(同期のずれた配列、「ひとりでに」変異したオブジェクト)は、
きれいにコンパイルされ、あとになって初めて表面化します。
ExcelMaster なら、
代わりにデータを説明できます。「従業員のリストについて名前・年齢・給与を追跡して、
給与で並べ替えたい」と言えば、レコードをモデル化してくれます — 何もずれないように
ソートを適用した Type の配列として — そして、代わりにクラスを選んだであろう場面を、
コードの中で説明してくれます。値と参照のルールをすべて頭の中に抱えておかなくても、
正しい構造が手に入ります。
よくある質問
VBA の Type とは何ですか?
Type(ユーザー定義型、UDT)は、関連する複数のフィールドを 1 つの名前のもとに
まとめるカスタムのデータ構造です — 例えば Name・Age・Salary を持つ Employee
です。標準モジュールの一番上で Type ... End Type を使って一度宣言すれば、あとは
他の型と同じように Dim e As Employee として使えます。関連する値を、別々でずれやすい
変数としてではなく、1 つの変数として一緒に運べるようにします。
VBA の Type とクラスモジュールの違いは何ですか?
Type はデータのみを保持する値型です — 一方を他方に代入するとすべての
フィールドがコピーされるので、2 つは独立しています。クラスモジュールは
データと振る舞い(メソッド)を保持する参照型で、Set b = a は両方の名前を
同じインスタンスに向けます。受動的なレコードには Type を、レコードにメソッドが
必要なとき、または Collection に格納しなければならないときはクラスを使います。
VBA で Type はどこで宣言しますか?
モジュールレベル、あらゆる手続きより上、標準モジュールの中です。Type を Sub
や Function の内側で宣言するとコンパイルエラーになります。プロジェクト全体で共有
するには Public Type(既定)を、1 つのモジュールに限定するには Private Type を
使います。Public Type をクラスモジュールの内側で宣言することはできません。
VBA の Type を Collection や Dictionary に格納できますか?
いいえ。Collection と Dictionary は Variant 値を格納しますが、ユーザー定義型は
Variant に変換できないので、coll.Add someType は失敗します。増やせる、あるいは
キー付きのレコードストアが必要なら、代わりにクラスモジュールを
使うか — そのインスタンスは Collection が受け入れるオブジェクトです — レコードを
Type の配列に保持してください。
ある Type 変数を別の変数に代入すると、データはコピーされますか、共有されますか?
コピーされます。 Type は値型なので、b = a はすべてのフィールドを独立した
変数に複製します。そのあと b を変えても a には影響しません。これはオブジェクト
(クラスのインスタンス)とは正反対で、そちらでは Set b = a が単一のインスタンスを
共有します。唯一の例外は Type を手続きに渡すときで、これは既定で ByRef なので、
呼び出し元のレコードを変更できます。
検証環境
検証環境: Excel 365 (Windows 11), VBA 7.1 — 最終確認 2026-08-04.
関連ガイド: VBA Class Module · VBA Property · VBA Data Types · VBA Array · VBA ByRef vs ByVal
