プロパティとは何か?
プロパティは、クラスモジュールで定義しているPrivate 変数を外部から取得・設定する関数のことを指します。クラスの場合は関数と言わずに「インターフェース」という言い方をしますが、ここではイメージしやすいように「関数」と書いています。
クラス内部のPrivate 変数はPrivate(非公開)のため外部から直接参照できません。しかし外部から変数値を参照したり変更したりする必要が出てきます。そのような場合にプロパティを用意します。
VBAのクラスでは以下の3種類のプロパティがあります。
構文 | 役割 | 引数 |
---|---|---|
Property Get プロパティ名() | 値の取得(読み取り) | 引数はありません。 |
Property Let プロパティ名(ByVal value As 値型) | 値の設定(書き込み、値型用) | クラスのPrivate変数に設定する値を指定します。 |
Property Set プロパティ名(ByVal value As オブジェクト型) | 値の設定(書き込み、オブジェクト用) | クラスのPrivate変数に設定するオブジェクト型の値を指定します。 |
Letの引数の値型とは、Integer、Long、Double、String、Boolean などの値型データを対象としています。
Setの引数のオブジェクト型とは、Object、Range、Dictionary、自作クラス などのオブジェクト型データを対象としています。
オブジェクト型についての詳細は「VBAのオブジェクトとは?分かる言葉で説明します」をご参照ください。
プロパティの引数はByRefではなくByValを使うべき理由
プロパティのLet と Setの引数で、プロパティの Let および Set の引数には、通常 ByVal(値渡し)を使うべきとされています。
その理由は、ByRefを使うと、呼び出し元の変数が意図せず書き換えられる可能性があるためです。これは、クラス内部の実装を変更しただけで外部に影響を及ぼすことになり、バグの原因になります。
通常のプロパティは「クラス内部のPrivate変数に値をセットすること」が目的であり、外部に影響を与えないようにするためにも ByVal を使うのが安全です。
一応以下のように「ByRef」で渡せますが、こういうコードはやめておきましょう。
この例では、渡された値そのものが関数内で書き換わっていることが確認できます。
1 2 3 4 5 |
Property Let Age(ByRef value As Integer) Debug.Print "Before change: " & value value = value + 1 Debug.Print "After change: " & value End Property |
プロパティ Get / Let / Setの違いと使い分け(値型・オブジェクト型)
数値や文字列(=値型)のプロパティなら Let を使い、オブジェクト(Dictionary や Range など)なら Set を使います。
以下はGet、Let、Setでのサンプルです。
なお、コード内でDictionaryを使っていますが、Dictionaryを使う場合は事前にVBA画面のツールメニュー→参照設定を選び、参照設定ダイアログで「Microsoft Scripting Runtime」にチェックを付けます。
Dictionaryの詳細については「VBAのDictionaryの使い方(全メソッドとプロパティ網羅)」をご参照ください。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
'// sample.cls '// 値型プロパティ Private mName As String '// 名前を取得するプロパティ Public Property Get Name() As String Name = mName End Property '// 名前を書き換えるプロパティ Public Property Let Name(ByVal value As String) mName = value End Property |
この例では、mName という Private(非公開)変数を定義し、Name プロパティを通して値の取得・設定を行っています。 Private 変数は外部から直接アクセスできませんが、プロパティを介することで安全にアクセスできるようになります。
ここでは単純に変数の値を返しているだけですが、今後プロパティ内部で値の加工処理やエラーチェックなどを追加する場合、プロパティ経由のほうが柔軟性が高くなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
'// sample2.cls '// オブジェクト型プロパティ(ここではDictionaryクラスを利用する) Private mDict As Scripting.Dictionary '// クラス内部のDictionary型変数値を取得するプロパティ Public Property Get Dict() As Scripting.Dictionary Set Dict = mDict End Property '// クラス内部のDictionary型変数値を書き換えるプロパティ Public Property Set Dict(ByVal value As Scripting.Dictionary) Set mDict = value End Property |
オブジェクト型の場合も値型と考え方は同じで、LetとSetが違うぐらいですが、オブジェクト型のプロパティでは、「Set」を使って「オブジェクトの参照」を正しく代入する必要があります。「Set」を使わずに代入すると、エラーになるので注意してください。
読み取り専用プロパティの実装(Get のみ定義して変更不可に)
一度設定したら外部から変更させたくない情報は、Get のみを実装して「読み取り専用」にします。たとえば、クラスを識別する ID や、クラスが作成された日時などが該当します。
以下は「作成日時」を読み取り専用プロパティとして定義する例です。
1 2 3 4 5 6 7 8 9 10 11 |
Private mCreated As Date '// 作成日時 '// クラス初期処理 Private Sub Class_Initialize() mCreated = Now End Sub '// 作成日時を取得するプロパティ Public Property Get CreatedAt() As Date CreatedAt = mCreated End Property |
このコードでは値を参照するためのGetプロパティは用意していますが、値を書き換えるLetプロパティは「書き換えてほしくないのに書き換わってしまう」という問題を起こすため用意していません。
このコードでは、mCreated 変数にクラス生成時の日時を クラス開始時に呼び出されるClass_Initialize イベントで格納し、それをGetプロパティ経由で読み取れるようにしています。
書き換えを防ぐために、Let プロパティはあえて定義していません。これにより、外部から 作成日時のCreatedAt 変数の値を変更できない安全な設計になります。
書き込み専用プロパティの実装(Let のみ定義してセキュリティ向上)
ユーザーに「設定だけさせて、取得はさせたくない」ケースでは Let プロパティだけを定義することができます。典型的な例として「パスワード」などが挙げられます。
1 2 3 4 5 |
Private mPassword As String Public Property Let Password(ByVal value As String) mPassword = value End Property |
このように、パスワードのような機密性の高い情報は、外部から読み取られないように Get プロパティをあえて用意しません。書き込み専用にすることで、情報漏洩のリスクを減らすことができます。
プロパティで値をチェックする方法(例:年齢に負の数を禁止)
プロパティは単なる変数の代入/取得ではなく、関数として処理を記述できます。
そのため、Let や Set プロパティで値を受け取る際に、値の妥当性をチェックする処理を加えるのが一般的です。たとえば、クラスの内部状態を破損させないために、異常な値(負の年齢など)を弾くようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
'// sample3.cls '// 年齢 Private mAge As Long '// 年齢取得プロパティ Public Property Get Age() As Long Age = mAge End Property '// 年齢書き換えプロパティ Public Property Let Age(ByVal value As Long) '// 引数の年齢がマイナスの場合 If value < 0 Then '// エラーとして扱う Err.Raise vbObjectError + 513, , "年齢に負の数は指定できません。" End If mAge = value End Property |
このように、プロパティを経由することで、変数に直接代入するよりも柔軟なチェックや制御が可能になります。
オブジェクト型のプロパティ(Setを使う例)
Dictionary や Range など、オブジェクトを保持する場合は Property Set を使います。
Setプロパティの内部処理で引数オブジェクトをクラス内部のPrivate変数に代入を行う場合も、Setを付ける必要があります。この「Set」は「Property Set」とは関係なく、オブジェクトの代入を行う際に付ける「Set」ステートメントです。値型と異なり、Set ステートメントを忘れると、「型の不一致」などのエラーになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
'// sample4.cls '// 対象セル範囲 Private mTargetRange As Range '// 対象セル範囲を取得するプロパティ Public Property Get TargetRange() As Range Set TargetRange = mTargetRange End Property '// 対象セル範囲を変更するプロパティ Public Property Set TargetRange(ByVal value As Range) Set mTargetRange = value End Property |
オブジェクト型を扱うときは、取得(Get)にも Set ステートメントを使う必要がある点にも注意してください。また、不要になったオブジェクトを解放したい場合は以下のように Nothing を代入します。
1 |
Set mTargetRange = Nothing |
実用例:社員クラスのプロパティ活用パターン
Get、Let、Setの各プロパティをまとめたクラスモジュールのサンプルです。
コード内でDictionaryを使っていますが、Dictionaryを使う場合は事前にVBA画面のツールメニュー→参照設定を選び、参照設定ダイアログで「Microsoft Scripting Runtime」にチェックを付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
'// Employee.cls(クラスモジュール) Option Explicit Private mId As Long '// 社員ID Private mName As String '// 社員名 Private mSkills As Scripting.Dictionary '// スキル一覧(社員が複数のスキルを持つ。キー:スキル名、値:スキルレベル) '// クラスの初期処理 Private Sub Class_Initialize() Set mSkills = New Scripting.Dictionary End Sub '// クラスの終了処理 Private Sub Class_Terminate() '// スキル一覧を解放 Set mSkills = Nothing End Sub '// ID参照プロパティ Public Property Get Id() As Long Id = mId End Property '// ID変更プロパティ Public Property Let Id(ByVal value As Long) mId = value End Property '// 社員名参照プロパティ Public Property Get Name() As String Name = mName End Property '// 社員名変更プロパティ Public Property Let Name(ByVal value As String) mName = Trim(value) End Property '// スキル一覧取得プロパティ(Dictionary) Public Property Get Skills() As Scripting.Dictionary Set Skills = mSkills End Property '// スキル一覧変更プロパティ Public Property Set Skills(ByVal value As Scripting.Dictionary) Set mSkills = value End Property '// スキル追加用メソッド(外部から直接 Dictionary を操作しなくて済むように) Public Sub AddSkill(ByVal skillName As String, ByVal level As Integer) If skillName = "" Then Err.Raise vbObjectError + 1001, , "スキル名は空白にできません。" End If If level < 1 Or level > 5 Then Err.Raise vbObjectError + 1002, , "スキルレベルは1から5の範囲で指定してください。" End If mSkills(skillName) = level End Sub |
上の社員クラスを外部から使う場合のサンプルコードが以下になります。標準モジュールに記述して実行すると動作します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Sub TestEmployeeWithSkills() '// 社員クラスの領域確保 Dim emp As Employee Set emp = New Employee '// プロパティで値をセット emp.Id = 101 emp.Name = "田中 太郎" '// スキル追加 Call emp.AddSkill("VBA", 4) Call emp.AddSkill("Excel", 5) Call emp.AddSkill("SQL", 3) '// スキルを出力 Dim key As Variant Debug.Print "【社員】" & emp.Name & "(ID: " & emp.Id & ")のスキル一覧:" For Each key In emp.Skills.Keys Debug.Print "・" & key & "(スキルレベル " & emp.Skills(key) & ")" Next End Sub |
まとめ:プロパティの設計はクラスの質を左右する
プロパティの設計次第で、クラスは「使いやすく、安全で、信頼できるもの」になります。
以下がプロパティを作る際の指針になります。
1. Get / Let / Set の役割を正しく理解する
- Get: 外部に値を渡す(参照させる)
- Let: 外部から値を受け取って代入
- Set: 外部からオブジェクトを受け取って代入(Object 型の場合のみ)
値型(Long, Stringなど)とオブジェクト型(Range, Dictionaryなど)で使うキーワードが異なる点をしっかり意識することが重要です。ミスすると「型の不一致」エラーになります。
2. 外部から見えるべき情報と、隠すべき情報を明確に分ける
- すべての情報をプロパティで開放するのではなく、「必要な情報だけ」外に公開するのがクラス設計の基本です。
- クラスの内部データを不用意に操作させないことで、予期せぬバグやデータ破壊を防げます。
- 読み取り専用にしたい場合は Get のみ提供して Let や Set を作らない、などの工夫も大切です。
3. データの整合性はプロパティ内で守る
- 不正な値の代入をプロパティ内部でチェックすることで、クラスの状態を常に正しく保つことができます。
- 前セクションのように「負の値はエラーにする」「文字列をTrimする」「オブジェクトの初期化・解放を制御する」などは、まさにこれを実現するためのテクニックです。
- プロパティを通すことで「検証ロジックの集中管理」ができ、クラス利用者が直接ミスを起こす余地を減らせます。