🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

Excel VBA の Type — 関連するフィールドを 1 つの変数にまとめる(ユーザー定義型 vs クラス)

|

Excel VBA の Type — 関連するフィールドを 1 つの変数にまとめる(ユーザー定義型 vs クラス)

TL;DRTypeユーザー定義型、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 の内側に入れると、プロジェクトはコンパイルできません。これは、 TypeDim と同じように扱う人がはまるところです。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 = aab同じオブジェクトの 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 に変換できません。Typecoll.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 つの名前のもとに まとめるカスタムのデータ構造です — 例えば NameAgeSalary を持つ Employee です。標準モジュールの一番上で Type ... End Type を使って一度宣言すれば、あとは 他の型と同じように Dim e As Employee として使えます。関連する値を、別々でずれやすい 変数としてではなく、1 つの変数として一緒に運べるようにします。

VBA の Type とクラスモジュールの違いは何ですか?

Typeデータのみを保持する値型です — 一方を他方に代入するとすべての フィールドがコピーされるので、2 つは独立しています。クラスモジュールは データ振る舞い(メソッド)を保持する参照型で、Set b = a は両方の名前を 同じインスタンスに向けます。受動的なレコードには Type を、レコードにメソッドが 必要なとき、または Collection に格納しなければならないときはクラスを使います。

VBA で Type はどこで宣言しますか?

モジュールレベル、あらゆる手続きより上、標準モジュールの中です。TypeSubFunction の内側で宣言するとコンパイルエラーになります。プロジェクト全体で共有 するには Public Type(既定)を、1 つのモジュールに限定するには Private Type を 使います。Public Type をクラスモジュールの内側で宣言することはできません。

VBA の Type を Collection や Dictionary に格納できますか?

いいえ。CollectionDictionaryVariant 値を格納しますが、ユーザー定義型は 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