クラスモジュール

にメンテナンス済み

VBA で複雑なマクロを書いていると、「関連する変数とプロシージャをまとめたい」「同じような処理を使いまわしたい」と感じることがあります。こうした課題を解決するのがクラスモジュールです。クラスモジュールを使えば、データ(プロパティ)とそのデータを操作する処理(メソッド)を一つの「部品」としてまとめ、独自のオブジェクトを作成できます。

この記事では、クラスモジュールの基本的な作り方から、実務で活用できる実践的なパターンまで詳しく解説します。

クラスモジュールとは

クラスモジュールは、VBA で独自のオブジェクト(カスタムオブジェクト)を定義するための仕組みです。Excel の WorkbookWorksheetRange がオブジェクトであるように、自分で設計した「オブジェクトの設計図」を作ることができます。

クラスとオブジェクトの関係

クラスとオブジェクトの関係は、「設計図」と「実体」の関係に例えられます:

  • クラス = 設計図(プロパティとメソッドの定義)
  • インスタンス(オブジェクト) = 設計図をもとに作った実体

例えば、「従業員」というクラスを設計図として作成し、そこから「田中太郎」「佐藤花子」といった具体的なインスタンスを生成する、というイメージです。

クラスモジュールを使うメリット

観点標準モジュールのみクラスモジュール活用
データと処理の関係バラバラに管理一つにまとめて管理
変数のスコープモジュールレベルで管理オブジェクト内にカプセル化
コードの再利用コピー&ペーストが必要モジュールをドラッグ&ドロップ
メンテナンス性変更の影響範囲が広い影響を局所化できる
IntelliSense利用しにくいプロパティ/メソッドが補完される
VBA のオブジェクト指向について

一般的なオブジェクト指向プログラミングには「カプセル化」「継承」「多態性(ポリモーフィズム)」の3大要素がありますが、VBA ではカプセル化のみがサポートされています。継承は直接サポートされていませんが、Implements キーワードによるインターフェースの実装は可能です。

クラスモジュールの作成と基本

クラスモジュールの挿入方法

  1. VBE(Visual Basic Editor)を開く(Alt + F11
  2. メニューバーから「挿入」→「クラスモジュール」を選択
  3. プロパティウィンドウの (Name) でクラス名を設定(例: CEmployee
クラス名の命名規則

クラス名の先頭に C を付ける(例: CEmployeeCProduct)慣例がよく使われます。これにより標準モジュールやワークシートオブジェクトと区別しやすくなります。ただし、必須ではありません。

最初のクラスを作る

まず、従業員情報を管理するシンプルなクラスを作成します。

CEmployee
' === クラスモジュール: CEmployee ===

' プライベート変数(外部から直接アクセスできない)
Private pId As Long
Private pName As String
Private pDepartment As String

' --- プロパティ ---

' Id プロパティ(読み書き)
Public Property Get Id() As Long
    Id = pId
End Property

Public Property Let Id(ByVal value As Long)
    pId = value
End Property

' Name プロパティ(読み書き)
Public Property Get Name() As String
    Name = pName
End Property

Public Property Let Name(ByVal value As String)
    pName = value
End Property

' Department プロパティ(読み書き)
Public Property Get Department() As String
    Department = pDepartment
End Property

Public Property Let Department(ByVal value As String)
    pDepartment = value
End Property

' --- メソッド ---

' 従業員情報を文字列で返す
Public Function GetInfo() As String
    GetInfo = "ID: " & pId & ", 名前: " & pName & ", 部署: " & pDepartment
End Function

' 従業員情報をイミディエイトウィンドウに表示
Public Sub PrintInfo()
    Debug.Print GetInfo()
End Sub

クラスのインスタンス化と使用

標準モジュールからクラスを使用します。

クラスの使用例
Sub UseEmployeeClass()
    ' インスタンスの生成
    Dim emp As CEmployee
    Set emp = New CEmployee

    ' プロパティの設定
    emp.Id = 1001
    emp.Name = "田中太郎"
    emp.Department = "営業部"

    ' メソッドの呼び出し
    emp.PrintInfo  ' => ID: 1001, 名前: 田中太郎, 部署: 営業部

    ' 複数のインスタンスを作成できる
    Dim emp2 As CEmployee
    Set emp2 = New CEmployee
    emp2.Id = 1002
    emp2.Name = "佐藤花子"
    emp2.Department = "開発部"

    emp2.PrintInfo  ' => ID: 1002, 名前: 佐藤花子, 部署: 開発部

    ' オブジェクトの解放
    Set emp = Nothing
    Set emp2 = Nothing
End Sub

プロパティの種類

クラスのプロパティは、3 種類の Property プロシージャで定義します。

Property Get - 値の取得

外部からプロパティの値を読み取るときに使用します。

property_get.bas
Public Property Get Name() As String
    Name = pName
End Property

Property Let - 値の設定(値型)

外部からプロパティに値型(String、Long、Boolean 等)の値を設定するときに使用します。

property_let.bas
Public Property Let Name(ByVal value As String)
    ' バリデーションも追加できる
    If Len(value) = 0 Then
        Err.Raise 5, , "名前は空にできません。"
    End If
    pName = value
End Property

Property Set - 値の設定(オブジェクト型)

外部からプロパティにオブジェクト型(Worksheet、Range 等)の値を設定するときに使用します。

property_set.bas
Private pSheet As Worksheet

Public Property Get Sheet() As Worksheet
    Set Sheet = pSheet
End Property

Public Property Set Sheet(ByVal value As Worksheet)
    Set pSheet = value
End Property
Property Let と Set の違い
VBA でクラスを作成し、 プロパティ(メンバー変数) を取り扱う際に作成する Property メソッドには Get, Set, Let があります。一般的に、VBA 以外の言語では、プロパティ(メンバー変数)に設定するのは Getter

読み取り専用プロパティ

Property Get だけを定義し、Property Let / Property Set を省略すると、読み取り専用のプロパティになります。

readonly_property.bas
Private pCreatedAt As Date

' 読み取り専用プロパティ
Public Property Get CreatedAt() As Date
    CreatedAt = pCreatedAt
End Property
チェック

Property Get のみを定義したプロパティは外部から値を設定できないため、意図しないデータの変更を防げます。これがカプセル化のメリットの一つです。

初期化と終了処理

Class_Initialize - コンストラクタ

クラスのインスタンスが生成されたとき(New したとき)に自動的に実行されるイベントです。プロパティの初期値設定に使用します。

class_initialize.bas
Private Sub Class_Initialize()
    ' デフォルト値の設定
    pId = 0
    pName = "未設定"
    pDepartment = "未所属"
    pCreatedAt = Now
End Sub

Class_Terminate - デストラクタ

クラスのインスタンスが破棄されたとき(Set obj = Nothing やスコープ外になったとき)に自動的に実行されるイベントです。クリーンアップ処理に使用します。

class_terminate.bas
Private Sub Class_Terminate()
    ' リソースの解放
    Set pSheet = Nothing
    Debug.Print "CEmployee インスタンスが破棄されました。"
End Sub
チェック

Class_Initialize は他のプログラミング言語の「コンストラクタ」、Class_Terminate は「デストラクタ」に相当します。ただし、VBA の Class_Initialize は引数を受け取れません。引数付きの初期化が必要な場合は、別途 Init メソッドを用意するパターンが一般的です。

Init メソッドパターン(引数付き初期化)

init_method.bas
' クラスモジュール: CEmployee

Public Sub Init(ByVal id As Long, ByVal empName As String, ByVal dept As String)
    pId = id
    pName = empName
    pDepartment = dept
End Sub
init_method_usage.bas
' 標準モジュールでの使用
Sub UseInitMethod()
    Dim emp As CEmployee
    Set emp = New CEmployee
    emp.Init 1001, "田中太郎", "営業部"

    emp.PrintInfo  ' => ID: 1001, 名前: 田中太郎, 部署: 営業部
End Sub

カプセル化の実践

カプセル化とは、データ(プロパティ)と処理(メソッド)を一つにまとめ、内部の実装を外部から隠蔽する考え方です。

Public 変数 vs Property プロシージャ

クラスのメンバ変数を Public で宣言するだけでもプロパティとして使えますが、バリデーションや加工処理ができません。

public_vs_property.bas
' === 方法1: Public 変数(シンプルだがバリデーション不可)===
Public Name As String
Public Age As Long

' === 方法2: Property プロシージャ(推奨)===
Private pName As String
Private pAge As Long

Public Property Get Age() As Long
    Age = pAge
End Property

Public Property Let Age(ByVal value As Long)
    ' バリデーション
    If value < 0 Or value > 150 Then
        Err.Raise 5, "CEmployee", "年齢は 0〜150 の範囲で指定してください。"
    End If
    pAge = value
End Property
チェック

Public 変数はシンプルですが、任意の値を自由に代入できてしまいます。重要なデータには Property Let でバリデーションを入れることを推奨します。ただし、シンプルなデータ保持だけが目的なら Public 変数でも十分です。

内部メソッドの隠蔽

クラス内部でのみ使用するヘルパーメソッドは Private で宣言します。

private_method.bas
' 外部から呼び出せるメソッド
Public Function GetFormattedInfo() As String
    GetFormattedInfo = FormatId() & " - " & pName & "(" & pDepartment & ")"
End Function

' 内部でのみ使用するヘルパーメソッド
Private Function FormatId() As String
    FormatId = Format(pId, "EMP-0000")
End Function

Collection と組み合わせたパターン

クラスのインスタンスを Collection に格納して管理するのは、VBA で最もよく使われるパターンの一つです。

class_with_collection.bas
Sub ManageEmployees()
    ' 従業員コレクションの作成
    Dim employees As Collection
    Set employees = New Collection

    ' データを読み込んでインスタンスを生成
    Dim ws As Worksheet
    Set ws = Sheets("従業員データ")

    Dim lastRow As Long
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    Dim i As Long
    For i = 2 To lastRow
        Dim emp As CEmployee
        Set emp = New CEmployee
        emp.Init ws.Cells(i, 1).Value, ws.Cells(i, 2).Value, ws.Cells(i, 3).Value

        ' キーとして ID を使って追加
        employees.Add emp, CStr(emp.Id)
    Next i

    ' コレクション内のオブジェクトを使用
    Dim e As CEmployee
    For Each e In employees
        Debug.Print e.GetInfo()
    Next e

    ' キーでオブジェクトを取得
    Dim target As CEmployee
    Set target = employees("1001")
    Debug.Print "検索結果: " & target.GetInfo()

    Set employees = Nothing
End Sub
Collectionオブジェクト
VBAのCollectionオブジェクトは、複数のデータを柔軟に管理できる便利なデータ構造です。この記事では、Collectionの基本的な使い方から、Dictionaryとの使い分け、実践的な活用方法まで詳しく解説します。
Dictionaryオブジェクト
VBAのDictionaryオブジェクトは、キーと値のペアでデータを管理できる強力なデータ構造です。この記事では、Dictionaryの基本的な使い方から実践的な活用方法まで、初心者にもわかりやすく解説します。

実践的な活用例

活用例1:処理時間計測クラス

マクロの処理時間を簡単に計測するためのユーティリティクラスです。

CStopwatch
' === クラスモジュール: CStopwatch ===

Private pStartTime As Double
Private pLabel As String

Private Sub Class_Initialize()
    pLabel = "処理"
End Sub

' 計測ラベルの設定
Public Property Let Label(ByVal value As String)
    pLabel = value
End Property

' 計測開始
Public Sub Start()
    pStartTime = Timer
End Sub

' 経過時間(秒)を取得
Public Function GetElapsed() As Double
    GetElapsed = Timer - pStartTime
End Function

' 経過時間をイミディエイトウィンドウに出力
Public Sub PrintElapsed()
    Debug.Print pLabel & ": " & Format(GetElapsed(), "0.000") & " 秒"
End Sub

' 経過時間をステータスバーに表示
Public Sub ShowInStatusBar()
    Application.StatusBar = pLabel & ": " & Format(GetElapsed(), "0.000") & " 秒"
End Sub
CStopwatch
Sub MeasurePerformance()
    Dim sw As CStopwatch
    Set sw = New CStopwatch
    sw.Label = "データ処理"
    sw.Start

    ' 処理を実行
    Dim i As Long
    For i = 1 To 100000
        ' 何らかの処理
    Next i

    sw.PrintElapsed  ' => データ処理: 0.125 秒

    Set sw = Nothing
End Sub

活用例2:ログ出力クラス

処理結果やエラー情報をログシートに記録するクラスです。

CLogger
' === クラスモジュール: CLogger ===

Private pLogSheet As Worksheet

Private Sub Class_Initialize()
    ' ログシートの取得(なければ作成)
    On Error Resume Next
    Set pLogSheet = ThisWorkbook.Sheets("Log")
    On Error GoTo 0

    If pLogSheet Is Nothing Then
        Set pLogSheet = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
        pLogSheet.Name = "Log"
        pLogSheet.Range("A1").Value = "日時"
        pLogSheet.Range("B1").Value = "レベル"
        pLogSheet.Range("C1").Value = "メッセージ"
        pLogSheet.Range("A1:C1").Font.Bold = True
    End If
End Sub

' ログの書き込み(共通処理)
Private Sub WriteLog(ByVal level As String, ByVal message As String)
    Dim nextRow As Long
    nextRow = pLogSheet.Cells(pLogSheet.Rows.Count, 1).End(xlUp).Row + 1

    pLogSheet.Cells(nextRow, 1).Value = Now
    pLogSheet.Cells(nextRow, 2).Value = level
    pLogSheet.Cells(nextRow, 3).Value = message

    ' レベルに応じた色分け
    Select Case level
        Case "ERROR"
            pLogSheet.Cells(nextRow, 2).Interior.Color = RGB(255, 200, 200)
        Case "WARN"
            pLogSheet.Cells(nextRow, 2).Interior.Color = RGB(255, 255, 200)
        Case "INFO"
            pLogSheet.Cells(nextRow, 2).Interior.Color = RGB(200, 230, 255)
    End Select
End Sub

' 情報ログ
Public Sub Info(ByVal message As String)
    WriteLog "INFO", message
End Sub

' 警告ログ
Public Sub Warn(ByVal message As String)
    WriteLog "WARN", message
End Sub

' エラーログ
Public Sub LogError(ByVal message As String)
    WriteLog "ERROR", message
End Sub
CLogger
Sub ProcessWithLogging()
    Dim logger As CLogger
    Set logger = New CLogger

    logger.Info "処理を開始します。"

    On Error GoTo ERROR_HANDLER

    ' データの処理
    Dim ws As Worksheet
    Set ws = Sheets("データ")
    Dim lastRow As Long
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    logger.Info lastRow - 1 & " 件のデータを処理します。"

    Dim i As Long
    Dim errorCount As Long
    For i = 2 To lastRow
        If IsEmpty(ws.Cells(i, 1).Value) Then
            logger.Warn "行 " & i & ": データが空です。スキップしました。"
            errorCount = errorCount + 1
        End If
    Next i

    logger.Info "処理が完了しました。エラー: " & errorCount & " 件"

    Set logger = Nothing
    Exit Sub

ERROR_HANDLER:
    logger.LogError "エラーが発生しました: " & Err.Description
    Set logger = Nothing
End Sub

活用例3:設定管理クラス

マクロの設定値を一元管理するクラスです。設定シートから値を読み込み、プロパティとして提供します。

CConfig
' === クラスモジュール: CConfig ===

Private pSettings As Object  ' Dictionary

Private Sub Class_Initialize()
    Set pSettings = CreateObject("Scripting.Dictionary")

    ' 設定シートから値を読み込み
    Dim ws As Worksheet
    On Error Resume Next
    Set ws = ThisWorkbook.Sheets("設定")
    On Error GoTo 0

    If ws Is Nothing Then Exit Sub

    Dim lastRow As Long
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    Dim i As Long
    For i = 2 To lastRow
        Dim key As String
        key = CStr(ws.Cells(i, 1).Value)
        If key <> "" Then
            pSettings(key) = ws.Cells(i, 2).Value
        End If
    Next i
End Sub

' 設定値の取得
Public Function GetValue(ByVal key As String, Optional ByVal defaultValue As Variant = "") As Variant
    If pSettings.Exists(key) Then
        GetValue = pSettings(key)
    Else
        GetValue = defaultValue
    End If
End Function

' 設定値の存在確認
Public Function HasKey(ByVal key As String) As Boolean
    HasKey = pSettings.Exists(key)
End Function

Private Sub Class_Terminate()
    Set pSettings = Nothing
End Sub
CConfig
Sub UseConfig()
    Dim config As CConfig
    Set config = New CConfig

    ' 設定シートの値を取得
    Dim outputPath As String
    outputPath = config.GetValue("出力先フォルダ", "C:\output\")

    Dim maxRows As Long
    maxRows = CLng(config.GetValue("最大行数", 1000))

    Debug.Print "出力先: " & outputPath
    Debug.Print "最大行数: " & maxRows

    Set config = Nothing
End Sub

ユーザー定義型(Type)との比較

VBA にはクラス以外にも、関連するデータをまとめる**ユーザー定義型(Type)**があります。

type_vs_class.bas
' === ユーザー定義型(Type)===
Type Employee
    Id As Long
    Name As String
    Department As String
End Type

' === クラス ===
' CEmployee クラスモジュール(前述)
項目ユーザー定義型(Type)クラスモジュール
メソッドの定義× 不可○ 可能
バリデーション× 不可○ Property で実装可能
カプセル化× 不可○ Private / Public で制御
IntelliSense○ メンバが補完される○ プロパティ/メソッドが補完される
初期化/終了処理× 不可○ Initialize / Terminate
Collection に格納× 不可○ 可能
パフォーマンス○ 高速△ やや遅い
複雑さ○ シンプル△ やや複雑
チェック

データの保持だけが目的なら Type で十分です。メソッドやバリデーションが必要な場合はクラスモジュールを使いましょう。

注意点とベストプラクティス

1. オブジェクトの解放

クラスのインスタンスを使い終わったら、Set obj = Nothing で明示的に解放しましょう。特にループ内でインスタンスを生成する場合は重要です。

2. クラス名は明確に

クラス名は何を表すオブジェクトかが一目でわかる名前にしましょう。C プレフィックスを付ける慣例が広く使われています(CEmployeeCLoggerCConfig)。

3. Public 変数は必要に応じて使う

すべてのプロパティに Property Get / Property Let を書く必要はありません。シンプルなデータ保持だけなら Public 変数でも構いません。バリデーションや読み取り専用が必要な場合に Property プロシージャを使い分けましょう。

4. クラスの粒度を適切に保つ

一つのクラスに詰め込みすぎず、責務を明確にしましょう。「ログを管理するクラス」「設定を管理するクラス」「従業員データを表すクラス」のように、一つのクラスが一つの役割を持つのが理想です。

5. モジュールの再利用

クラスモジュールは VBE のプロジェクトエクスプローラーからドラッグ&ドロップで別のプロジェクトにコピーできます。汎用的なクラスを一度作成すれば、複数のマクロで再利用できるのが大きなメリットです。

Enum(列挙型)
VBAのEnum(列挙型)は、関連する数値定数をグループ化して管理できる便利な機能です。この記事では、Enumの基本的な構文から、列番号の管理やフラグ演算といった実践的な活用方法まで詳しく解説します。
Sub と Function の違い
VBAのSubプロシージャとFunctionプロシージャの違いを初心者向けに徹底解説。戻り値の有無、引数の渡し方(ByRef・ByVal)、スコープ(Public・Private)、呼び出し方法まで、実践的なコード例とともに紹介します。

練習問題

VBA のクラスモジュールで、インスタンスが生成されたときに自動的に実行されるイベントプロシージャはどれですか?

正解は Class_Initialize です。New キーワードでインスタンスを生成したときに自動的に実行され、プロパティの初期値設定などに使用します。他のプログラミング言語の「コンストラクタ」に相当しますが、引数を受け取ることはできません。

クラスのプロパティにオブジェクト型(Worksheet や Range 等)の値を設定するために使用する Property プロシージャはどれですか?

正解は Property Set です。Property Let は値型(String、Long 等)の設定に使用し、Property Set はオブジェクト型の設定に使用します。Property Get は値の取得用です。

以下のうち、クラスモジュールでカプセル化を実現するために最も重要な要素はどれですか?

正解は「メンバ変数を Private で宣言し、Property プロシージャで公開する」です。カプセル化の核心は、データの直接アクセスを制限し、公開されたインターフェース(Property プロシージャ)を通じてのみアクセスさせることです。これにより、バリデーションの追加や内部実装の変更が外部に影響しなくなります。

まとめ

  • クラスモジュールを使うと、データ(プロパティ)と処理(メソッド)をまとめた独自のオブジェクトを作成できる
  • クラスは「設計図」であり、New キーワードで「インスタンス(実体)」を生成して使用する
  • Property Get / Let / Set プロシージャでプロパティを定義し、バリデーションや読み取り専用の制御が可能になる
  • Class_InitializeClass_Terminate でインスタンスの初期化とクリーンアップを行う
  • VBA のオブジェクト指向ではカプセル化が最も重要な概念で、Private 変数と Property プロシージャで実現する
  • シンプルなデータ保持だけなら Public 変数やユーザー定義型(Type)で十分、メソッドやバリデーションが必要な場合にクラスを使う
  • 汎用的なクラスは別のプロジェクトにドラッグ&ドロップでコピーでき、再利用性が高い
#VBA #クラスモジュール #オブジェクト指向 #カプセル化 #クラス