和風スパゲティのレシピ

日本語でコーディングするExcelVBA

Property Let と Property Set の違い

プロシージャの種類である、「Property Letプロシージャ」と「Property Setプロシージャ」の違いを解説します。

同じところ

どちらもプロシージャであり、いわゆる(広い意味での)関数ですが、
両者とも、ちょっと特殊なプロシージャです。

これ以外の3種類のプロシージャ「Subプロシージャ」「Functionプロシージャ」「Property Getプロシージャ」とは呼び出し方が全く異なります。


ひとまずProperty Letプロシージャを例にすると、

Property Let A1セルの値(設定する値 As Long)
    Range("A1").Value = 設定する値
End Property

このように定義したProperty Letプロシージャを呼び出す場合は、

A1セルの値 = 1

と、「プロシージャの名前 = 値」という形で呼び出します。

この「値」が引数である「設定する値」に渡されて、中身の処理が実行されます。


この仕組みは「Property Getプロシージャ」と対をなすためのものであり、
例えば、

Property Get A1セルの値() As Long
    A1セルの値 = Range("A1").Value
End Property

Property Let A1セルの値(設定する値 As Long)
    Range("A1").Value = 設定する値
End Property

と、同じ名前のプロシージャ「A1セルの値」を、
Property LetとProperty Getの2つ用意することで、
 

A1セルの値 = 1
Debug.Print A1セルの値 ' = 1

と、A1セルの値をあたかも変数を扱っているかのように記述できます。


このように、
「値の取得」と「値の設定」を、1つの変数ではなく、
2つの関数で行う仕組みがPropertyプロシージャ
であり、

この関数のペアのうち、
「値の設定」を担うのが、「Property Let/Set」プロシージャです。

違うところ

先ほど「値の取得と設定を、1つの変数ではなく、2つの関数で」と書きましたが、

Propertyプロシージャを使わずに、普通に「1つの変数」でやる場合は、

Dim 数値 As Long

数値 = 1
Debug.Print 数値 ' = 1

と、ただ変数を宣言すればよいです。

当たり前ですね。


さてこの変数がオブジェクト変数である場合は、

Dim セル As Range

Set セル = Range("A1")
Debug.Print セル.Address ' = $A$1

と、ほぼ同じように書けますが、

代入時に「Setキーワード」が必要になりますよね?



これがそっくりそのままProperty Let とProperty Set の違いです。
 

関数の定義を見て、

Property Get 何かのプロパティ() As △△
        ' ⇅対になったペア
Property ○○ 何かのプロパティ(設定するもの As △△)

この△△がオブジェクトなら○○にはSet、そうでないならLetを使います


あるいは、代入時の書き方が、

何かのプロパティ = 設定するもの ' Property Let を使う
Set 何かのプロパティ = 設定するもの ' Property Set を使う

と解釈してもOKです。


このように、Property GetとProperty Letの違いは、

数値 = 1
Set セル = Range("A1")

この違いの、Propertyプロシージャ版だと思ってください。

使い分け

↑で記した通り、

Property Get 何かのプロパティ() As △△
        ' ⇅対になったペア
Property ○○ 何かのプロパティ(設定するもの As △△)

この△△がオブジェクトなら○○にはSet、そうでないならLetを使います。


Property Setは、オブジェクトを出し入れするため、
大抵は裏に本体となるオブジェクトを持ちます。

Private 隠されし真のセル As Range

Property Get セル() As Range
    Set セル = 隠されし真のセル
End Property

Property Set セル(設定するセル As Range)
    Set 隠されし真のセル = 設定するセル
End Property

 
こんな風に対となる関数を作ることで、
Propertyプロシージャの目的の通り、

Set セル = Range("A1")
Debug.Print セル.Address ' = $A$1

こう書くことができます。


「セル」という変数を処理しているかのように見えますが、
実際は「隠されし真のセル」に対して、
設定と値の参照が行われています。


これだけだと

Dim セル As Range

と何も変わらないので、

Property Set セル(設定するセル As Range)
    If 設定するセル.Parent Is Worksheets("秘密のシート") Then
        MsgBox("見ちゃダメ!")
    Else
        Set 隠されし真のセル = 設定するセル
    End If
End Property

のように、代入時に何らかの処理や制約を裏に持たせるときに使います。
これをオブジェクト指向用語で「隠蔽」と呼びます。


ちなみに「隠されし真の」という変数名は、

Private セル_ As Range

のように、「_」で表現されることが多いです。


Letでも当然同じことができ、

Private へそくり_ As Long

Property Get へそくり() As Long
    If Getユーザー名 <> "奥様" Then へそくり = 0
    へそくり = へそくり_
End Property

Property Let へそくり(設定する値 As Long)
    If Getユーザー名 <> "奥様" Then Exit Property
    へそくり_ = 設定する値
End Property

と裏の変数を隠すようにPropertyプロシージャを書けます。


Letはただの値なので、さらに柔軟な書き方が可能で、

冒頭のように、

Property Get A1セルの値() As Long
    A1セルの値 = Range("A1").Value
End Property

Property Let A1セルの値(設定する値 As Long)
    Range("A1").Value = 設定する値
End Property

と、何かのプロパティ(今回はRangeオブジェクトのValueプロパティ)を包んで、あたかも変数のように使うこともできますし、


さらにはプロパティの域も超えて、例えばシートの保護なんかを、

Property Let 書き込みOKスイッチ(設定する真偽値 As Boolean)

    If 設定する真偽値 = True Then
        WS集計シート.Unprotect
    Else
        WS集計シート.Protect
    End If

End Property

と書いておくと、

書き込みOKスイッチ = True

という代入で、シートの保護をつけ外しできるようにもなります。
こんな感じで、「処理をプロパティに」することすらできますね。



まとめると、

  • 隠されし真の変数がオブジェクトならProperty Set
  • 隠されし真の変数がオブジェクトでないならProperty Let
  • 他にもいろいろ便利なProperty Let

を使ってください。


上2つが理解できていればPropertyで困ることはないと思います。
下はおまけ程度に思ってください。

おまけ:実はオブジェクトもいけるLetさん

ここからの話はちょっと混乱する可能性があるので、
あまり深く考えずに読んでください(笑)


実はLetさん、オブジェクトもいける口です。
 

Private セル_ As Range

Property Get セル() As Range
    Set セル = セル_
End Property

Property Let セル(設定するセル As Range)
    Set セル_ = 設定するセル
End Property

と、Property Letを使ってオブジェクトを扱うこともでき、
 

セル = Range("A1")
Debug.Print セル.Address ' = $A$1

これで動きます。



さて、Property Setの時と何が違うかわかります?



もう一度どうぞ

セル = Range("A1")
Debug.Print セル.Address ' = $A$1

 



そう、Setがないんです。


Property Letを使った場合は、代入時にSetキーワードが不要になります。

逆にSetを書くと、「プロパティの使い方が不正です」エラーが出ます。



さて、この書き方はヤバいトラップを生みます。


なぜなら、

セル = Range("A1")

この左辺が、「Valueプロパティの省略」ではなくなるからです。


どう見ても「セルに、A1の値をコピー」しているように見えますが、
実際はセルの参照が変わっているのですから、たまったものではないです。


「Setを間違えてLetと書いてしまって」
「さらに代入時のSetを書き忘れる」

というバグにバグを重ねたときに偶然発生しますが、
マイナスとマイナスでプラスになるかのように、エラーが出なくなります。


しかし実際はよりでっかいマイナスになっているだけで、
割と深刻なバグ(エラー警告が出ずに対象を破壊)になるので、
頭の片隅に置いておいてください。


ちなみに、

x = 1

は、

Let x = 1

の省略ということはご存知ですか?


これをご存知の方には、

  • Property Let はLetステートメントで呼び出されるプロシージャ
  • Property Set はSetステートメントで呼び出されるプロシージャ

これが「Property LetとProperty Setの違い」を一番簡潔かつ正確に説明したテキストだと思います。


今までの仕様の話全部と、今回のLetさんのお茶目に、
すべて納得がいく説明になると思います。


Property Setプロシージャで定義したプロパティでは、
Setステートメントでは当然そのプロシージャが処理され、
その時のLetステートメントではProperty Getプロシージャが呼び出され、
その返り値オブジェクトの規定プロパティへの代入が処理されます。

Property Letプロシージャで定義したプロパティでは、
Letステートメントでは当然そのプロシージャが処理され、
その時定義のないSetステートメントはエラーを返します。


スッキリしますね。


いやProperty Letプロシージャがオブジェクトの代入をを許してしまうことにはスッキリできませんが(笑)



ちなみにこの逆の「Property Setでただの値を引数にする」はできません。
 

Property Set 数値(設定する値 As Long)
    数値_ = 設定する値
End Property

という関数を書いても、

同じプロパティに対するプロパティ プロシージャの定義が一致していません。またはプロパティ プロシージャに省略可能な引数または ParamArray が含まれいているか、Property Set の最後の引数が不正です。

という、どんだけ「または」言うねんっていうエラーをくれます。


このメッセージのうち、どれが原因なのかをVBAさんが特定できない理由はよくわかりませんが、今回は最後の「Property Set の最後の引数が不正です」が該当ですね。


分かりにくいエラーテキストですが、
報告するだけLetより偉いです。

許してあげましょう。


まあこの辺の話は深く考えるとこんがらがってくるので、
とりあえずは、

オブジェクトはSet、値はLetというルールをしっかり守ろう

と思っておけばOKです。