概要
Excel VBAにおいて、Property Get、Property Let、Property Setステートメントは、クラスモジュール内でオブジェクトのプロパティを操作するための非常に強力かつ柔軟なメカニズムを提供します。これらを理解し使いこなすことで、よりカプセル化され、保守性が高く、再利用可能なコードを作成することが可能になります。
従来の標準モジュールやフォームモジュールでは、直接変数を宣言し、それを操作することが一般的です。しかし、クラスモジュールでは、オブジェクトの内部状態を外部から直接アクセスさせたくない場合があります。例えば、あるプロパティの値を設定する際に、特定の条件を満たしているかチェックしたい、あるいは値を設定する際に別の連動するプロパティも自動的に更新したい、といったケースです。
Property Getステートメントは、プロパティの値を取得する際に実行されるコードを定義します。ユーザーがプロパティを参照した際に、このステートメント内のコードが実行され、定義された値が返されます。
Property Letステートメントは、プロパティに値を代入する際に実行されるコードを定義します。ユーザーがプロパティに値を代入しようとした際に、このステートメント内のコードが実行され、渡された値に基づいて内部の変数などが更新されます。
Property Setステートメントは、オブジェクト型のプロパティにオブジェクトへの参照を代入する際に実行されるコードを定義します。Property Letと似ていますが、こちらはオブジェクトを扱う場合に特化しています。
これらのステートメントを組み合わせることで、プロパティへのアクセスを制御し、データの整合性を保ち、より洗練されたオブジェクト指向プログラミングを実現できます。本記事では、これらのステートメントの基本的な使い方から、実際の応用例、そして実務で役立つアドバイスまでを網羅的に解説していきます。
詳細解説
Property Getステートメント
Property Getステートメントは、クラスモジュールのプロパティから値を取得する際に呼び出されます。構文は以下の通りです。
Public Property Get PropertyName(arguments) As DataType
‘ プロパティの値を取得するためのコード
PropertyName = value
End Property
* `Public`: プロパティを外部からアクセス可能にするためのキーワードです。`Private`や`Friend`も指定できます。
* `Property Get`: プロパティの値を取得するためのステートメントであることを示します。
* `PropertyName`: 定義するプロパティの名前です。
* `arguments`: オプションで、プロパティを取得する際に渡される引数です。通常、プロパティ取得時には引数は使用しません。
* `As DataType`: プロパティが返す値のデータ型を指定します。
* `PropertyName = value`: この部分で、実際に返される値を `PropertyName` に代入します。この `value` は、クラスモジュール内で宣言されたプライベート変数などになります。
例として、社員クラスに「名前」というプロパティを定義し、その値を取得するProperty Getを作成してみましょう。
‘ クラスモジュール (例: clsEmployee)
Private m_Name As String
Public Property Get Name() As String
‘ m_Name の値を返す
Name = m_Name
End Property
‘ 別のプロパティやメソッドも定義可能
‘ …
このクラスのインスタンスを作成し、`Name` プロパティを参照すると、Property Get `Name` プロシージャが実行され、`m_Name` の値が返されます。
Property Letステートメント
Property Letステートメントは、クラスモジュールのプロパティに値を代入する際に呼び出されます。構文は以下の通りです。
Public Property Let PropertyName(arguments) As DataType
‘ プロパティに値を設定するためのコード
‘ …
PropertyName = value
End Property
* `Public`: プロパティを外部からアクセス可能にするためのキーワードです。
* `Property Let`: プロパティに値を代入するためのステートメントであることを示します。
* `PropertyName`: 定義するプロパティの名前です。
* `arguments`: プロパティに代入される値を受け取るための引数です。この引数のデータ型は、プロパティのデータ型と一致させる必要があります。
* `As DataType`: この指定はProperty Letでは必須ではありませんが、引数のデータ型を明示するために使用されることがあります。
* `PropertyName = value`: この部分で、渡された引数の値を、クラスモジュール内のプライベート変数などに代入します。
社員クラスの例で、「名前」プロパティに値を設定するProperty Letを作成してみましょう。
‘ クラスモジュール (例: clsEmployee)
Private m_Name As String
Public Property Let Name(ByVal vNewValue As String)
‘ 値が空でないかなどのバリデーションを行うことも可能
If Len(vNewValue) > 0 Then
m_Name = vNewValue
Else
MsgBox “名前は空にできません。”, vbExclamation
‘ エラー処理やデフォルト値の設定なども可能
End If
End Property
‘ Property Get Name も定義されていると仮定
‘ …
このクラスのインスタンスを作成し、`emp.Name = “山田太郎”` のように代入すると、Property Let `Name` プロシージャが実行され、`vNewValue` に “山田太郎” が渡され、`m_Name` が更新されます。
Property Setステートメント
Property Setステートメントは、オブジェクト型のプロパティにオブジェクトへの参照を代入する際に使用します。Property Letと似ていますが、こちらはオブジェクトを代入する場合に限定されます。構文は以下の通りです。
Public Property Set PropertyName(arguments)
‘ プロパティにオブジェクト参照を設定するためのコード
‘ …
Set PropertyName = ObjectReference
End Property
* `Public`: プロパティを外部からアクセス可能にするためのキーワードです。
* `Property Set`: プロパティにオブジェクト参照を代入するためのステートメントであることを示します。
* `PropertyName`: 定義するプロパティの名前です。
* `arguments`: 代入されるオブジェクト参照を受け取るための引数です。この引数はオブジェクト型である必要があります。
* `Set PropertyName = ObjectReference`: この部分で、渡されたオブジェクト参照を、クラスモジュール内のプライベートなオブジェクト変数などに代入します。`Set` キーワードが必須です。
例として、会社クラス (`clsCompany`) があり、その会社に所属する社員オブジェクト (`clsEmployee`) を管理するプロパティを定義してみましょう。
‘ クラスモジュール (例: clsCompany)
Private m_Employee As clsEmployee
Public Property Set Employee(ByVal oNewEmployee As clsEmployee)
‘ 渡されたオブジェクトが有効かチェックすることも可能
If Not oNewEmployee Is Nothing Then
Set m_Employee = oNewEmployee
Else
MsgBox “有効な社員オブジェクトを指定してください。”, vbExclamation
End If
End Property
Public Property Get Employee() As clsEmployee
Set Employee = m_Employee
End Property
‘ …
このクラスのインスタンスを作成し、社員オブジェクトを代入する場合、`Set cmp.Employee = emp` のように記述します。`Set` キーワードが重要です。
Property Get/Let/Set の組み合わせ
これらのステートメントは単独で使われることもありますが、多くの場合、組み合わせて使用することで、より堅牢なクラスを作成できます。例えば、あるプロパティの設定時に、別のプロパティの値も連動して更新されるようなロジックを実装できます。
ある商品クラス (`clsProduct`) を考え、商品名 (`ProductName`) と価格 (`Price`) を持つとします。価格が変更されたときに、税込み価格 (`PriceWithTax`) も自動的に更新されるようにしたい場合、以下のような実装が考えられます。
‘ クラスモジュール (例: clsProduct)
Private m_ProductName As String
Private m_Price As Double
Private Const TAX_RATE As Double = 0.1 ‘ 税率 10%
Public Property Let ProductName(ByVal vNewValue As String)
m_ProductName = vNewValue
End Property
Public Property Get ProductName() As String
ProductName = m_ProductName
End Property
Public Property Let Price(ByVal vNewValue As Double)
If vNewValue >= 0 Then
m_Price = vNewValue
‘ 価格が変更されたら、税込み価格も再計算
‘ (ただし、PriceWithTax は Get のみで Let/Set は定義しない)
Else
MsgBox “価格は0以上で入力してください。”, vbExclamation
End If
End Property
Public Property Get Price() As Double
Price = m_Price
End Property
‘ 税込み価格は計算値なので、Get のみで実装
Public Property Get PriceWithTax() As Double
PriceWithTax = m_Price * (1 + TAX_RATE)
End Property
‘ 外部から直接 PriceWithTax に値を設定させたくない場合
‘ Public Property Let PriceWithTax(ByVal vNewValue As Double)
‘ ‘ 何もしない、あるいはエラーメッセージを表示
‘ End Property
この例では、`Price` プロパティに値を設定する (`Property Let Price`) 際に、内部の `m_Price` を更新し、さらに `PriceWithTax` プロパティの値も自動的に最新の状態に保たれます。`PriceWithTax` は計算値であり、外部から直接設定させる必要がないため、`Property Get` のみで実装しています。もし外部から `PriceWithTax` に値を設定しようとしても、`Property Let` が定義されていなければエラーになります。
引数を持つ Property Get
通常、Property Getは引数を取りませんが、特定の状況下では引数を持つことも可能です。これは、配列などのインデックスを指定して要素を取得する場合に便利です。
例えば、複数の商品名を持つクラスがあったとして、インデックスで商品名を取得したい場合などです。
‘ クラスモジュール (例: clsInventory)
Private m_ProductNames() As String
Public Property Let AddProduct(ByVal sProductName As String)
‘ 配列のサイズを拡張して商品名を追加
If UBound(m_ProductNames) = -1 Then ‘ 初めて追加する場合
ReDim m_ProductNames(0)
Else
ReDim Preserve m_ProductNames(UBound(m_ProductNames) + 1)
End If
m_ProductNames(UBound(m_ProductNames)) = sProductName
End Property
‘ 引数 (インデックス) を持つ Property Get
Public Property Get Product(ByVal lIndex As Long) As String
If lIndex >= LBound(m_ProductNames) And lIndex <= UBound(m_ProductNames) Then
Product = m_ProductNames(lIndex)
Else
Product = "無効なインデックスです。"
End If
End Property
' 全ての商品名を取得するプロパティ (例: Products)
Public Property Get Products() As Variant
Products = m_ProductNames
End Property
この場合、`clsInventory.Product(0)` のようにインデックスを指定して個々の商品名を取得できます。`Property Get Product` は引数 `lIndex` を持ちます。
サンプルコード
ここでは、これまでに解説した内容を統合した、より実践的なサンプルコードを示します。
#### クラスモジュール: `clsPerson`
このクラスは、人物を表し、名前、年齢、および所属部署オブジェクトを持ちます。
‘===========================================================
‘ クラスモジュール名: clsPerson
‘ 説明: 人物を表すクラス
‘===========================================================
‘ — プライベート変数 —
Private m_Name As String
Private m_Age As Integer
Private m_Department As clsDepartment ‘ 所属部署オブジェクト
‘ — プロパティ: Name (文字列) —
‘ 値の設定 (Property Let)
Public Property Let Name(ByVal vNewName As String)
If Len(vNewName) > 0 Then
m_Name = vNewName
Else
Err.Raise 1001, “clsPerson”, “名前は空にできません。”
End If
End Property
‘ 値の取得 (Property Get)
Public Property Get Name() As String
Name = m_Name
End Property
‘ — プロパティ: Age (整数) —
‘ 値の設定 (Property Let)
Public Property Let Age(ByVal vNewAge As Integer)
If vNewAge >= 0 And vNewAge <= 120 Then ' 年齢の妥当性チェック
m_Age = vNewAge
Else
Err.Raise 1002, "clsPerson", "有効な年齢を入力してください (0~120)。"
End If
End Property
' 値の取得 (Property Get)
Public Property Get Age() As Integer
Age = m_Age
End Property
' --- プロパティ: Department (オブジェクト) ---
' オブジェクト参照の設定 (Property Set)
Public Property Set Department(ByVal oNewDepartment As clsDepartment)
' 渡されたオブジェクトが有効かチェック
If Not oNewDepartment Is Nothing Then
Set m_Department = oNewDepartment
Else
Err.Raise 1003, "clsPerson", "有効な部署オブジェクトを指定してください。"
End If
End Property
' オブジェクト参照の取得 (Property Get)
Public Property Get Department() As clsDepartment
Set Department = m_Department
End Property
' --- メソッド: Introduce ---
' 自己紹介を行うメソッド
Public Sub Introduce()
Dim sDepartmentName As String
If Not m_Department Is Nothing Then
sDepartmentName = m_Department.Name
Else
sDepartmentName = "未所属"
End If
Debug.Print "私の名前は" & m_Name & "、" & m_Age & "歳です。所属は「" & sDepartmentName & "」です。"
End Sub
' --- 初期化 (Optional) ---
' クラスのインスタンスが作成されたときに実行される
' Private Sub Class_Initialize()
' Debug.Print "clsPerson インスタンスが作成されました。"
' End Sub
' --- 終了処理 (Optional) ---
' クラスのインスタンスが解放されるときに実行される
' Private Sub Class_Terminate()
' Debug.Print "clsPerson インスタンスが解放されました。"
' End Sub
#### クラスモジュール: `clsDepartment`
部署を表すクラスです。
'===========================================================
' クラスモジュール名: clsDepartment
' 説明: 部署を表すクラス
'===========================================================
' --- プライベート変数 ---
Private m_Name As String
' --- プロパティ: Name (文字列) ---
' 値の設定 (Property Let)
Public Property Let Name(ByVal vNewName As String)
If Len(vNewName) > 0 Then
m_Name = vNewName
Else
Err.Raise 2001, “clsDepartment”, “部署名は空にできません。”
End If
End Property
‘ 値の取得 (Property Get)
Public Property Get Name() As String
Name = m_Name
End Property
#### 標準モジュール: `modMain`
これらのクラスを使用するメインのモジュールです。
‘===========================================================
‘ 標準モジュール名: modMain
‘ 説明: clsPerson および clsDepartment クラスの使用例
‘===========================================================
Sub UsePersonAndDepartment()
Dim emp1 As clsPerson
Dim emp2 As clsPerson
Dim hrDept As clsDepartment
Dim salesDept As clsDepartment
‘ — 部署オブジェクトの作成と設定 —
Set hrDept = New clsDepartment
On Error Resume Next ‘ エラーハンドリングを一時的に無効化
hrDept.Name = “人事部”
If Err.Number <> 0 Then
MsgBox “部署名設定エラー: ” & Err.Description
Err.Clear
End If
On Error GoTo 0 ‘ エラーハンドリングを元に戻す
Set salesDept = New clsDepartment
salesDept.Name = “営業部”
‘ — 人員オブジェクトの作成と設定 —
Set emp1 = New clsPerson
On Error Resume Next
emp1.Name = “佐藤一郎”
emp1.Age = 30
Set emp1.Department = hrDept ‘ Property Set Department を使用
If Err.Number <> 0 Then
MsgBox “emp1 設定エラー: ” & Err.Description
Err.Clear
End If
On Error GoTo 0
Set emp2 = New clsPerson
emp2.Name = “田中花子”
emp2.Age = 25
Set emp2.Department = salesDept
‘ — プロパティの取得とメソッドの実行 —
Debug.Print “— emp1 情報 —”
Debug.Print “名前: ” & emp1.Name ‘ Property Get Name を使用
Debug.Print “年齢: ” & emp1.Age ‘ Property Get Age を使用
If Not emp1.Department Is Nothing Then
Debug.Print “部署名: ” & emp1.Department.Name ‘ 関連オブジェクトのプロパティにアクセス
End If
emp1.Introduce ‘ メソッド実行
Debug.Print vbCrLf & “— emp2 情報 —”
emp2.Introduce
‘ — エラーケースのテスト —
Debug.Print vbCrLf & “— エラーテスト —”
Dim emp3 As clsPerson
Set emp3 = New clsPerson
On Error Resume Next ‘ エラーを捕捉するために使用
emp3.Name = “” ‘ 空
