t-hom’s diary

主にVBAネタを扱っているブログです。

VBA Propertyはフィールドのフリをしたメソッド ~ プロパティに値を代入するという表現は厳密には正しくない

プロパティーに値を代入するという表現をよく見かける。
私もつい最近までそのように表現していたけれど、これって厳密には間違ってるんじゃないかと思い始めている。
というのも、Propertyプロシージャを学習する際、この「代入」という表現が理解を難しくさせている気がするのだ。

プロパティに代入してるんじゃなくて、プロパティが代入している。

プロパティに値を代入するなんて言うけど、実際にProperty Letプロシージャ自体に値を代入している訳ではない。
どういうことなのか、コードを使って説明しよう。

たとえば以下のようなCounterクラスを作成する。

Private num As Long
Public Property Get Number() As Long
    Number = num
End Property

Public Property Let Number(n As Long)
    num = n
End Property

Public Sub Plus(Optional n As Long = 1)
    num = num + n
End Sub

標準モジュールには次のコードを張り付ける。

Sub hoge()
    Dim C As Counter
    Set C = New Counter
    C.Number = 10
    Debug.Print C.Number
End Sub

ここで、C.Number = 10という文は、あたかもプロパティに値を代入しているように見えるだろう。単にクラスを使う方は、そのように理解してもまあ差し支えない。

でもいざ自分でPropertyプロシージャを使おうと思ったら、「代入」という表現は理解を難しくさせる。
C.Number = 10という文が行っているのは、NumberのLetプロパティーへ引数を渡す行為である。NumberのLetプロパティは仮引数nで渡された10を受け取り、それをクラスのプライベート変数であるnumに代入している。

つまり代入という行為はあくまでPropertyプロシージャの手続きの中で書かれたコードによって行われるのであって、C.Number = 10という書き方が、Numberプロパティへの代入を意味する訳ではないのだ。

もっといえば、Numberプロパティが受け取った仮引数をどうするかはクラス設計者にゆだねられており、代入せずに破棄してしまうこともできる。
先ほどのCounterクラスでNumberのLetプロパティを書き換えてみた。

Private num As Long
Public Property Get Number() As Long
    Number = num
End Property

Public Property Let Number(n As Long)
    '何もしない
End Property

Public Sub Plus(Optional n As Long = 1)
    num = num + n
End Sub

これでもメインコードは動作するが、イミディエイトウインドウでは0が表示される。

Propertyプロシージャはオブジェクトのプロパティを作るために作られた特別なプロシージャであるが、「プロシージャ」というからには「手続き」なので、SubやFunctionに書くようなふつうの命令も記入できる。

こんな風に。

Public Property Let Number(n As Long)
    MsgBox "代入する、と見せかけて"
    MsgBox "受け取った「" & n & "」は破棄します。"
End Property

また、クラスモジュールで用いるイメージが強いPropertyプロシージャだが、クラスでなくても書くことができる。

以下は標準モジュールでPropertyを活用した例。
thom.hateblo.jp

さらにVBA自体でも、標準モジュールのPropertyが活用されている。
thom.hateblo.jp

まあ、あくまでプロパティを作るものとしてPropertyが用意されているので、あまり逸脱した使い方はお勧めできないが、ここで言いたかったのはPropertyだって、SubやFunctionと同じように「手続き」なんだということ。

Javaの用語で学ぶクラスモジュール

VBAにおけるPropertyが一体何者なのか明らかにするために、Java用語を借りてこようと思う。
今から説明する用語はJavaに限った話ではなくてオブジェクト指向の一般的な用語だとは思うけれど、それ以外の言語に疎いのと、現にVBAでは聞かない用語もあるので。

まずクラスというものは、フィールドとメソッドから成る。
フィールドというのは、クラスが配下に持つ変数だ。メソッドというのはいわゆるプロシージャである。
そしてフィールドとメソッドを包括して、メンバと呼ぶ。
f:id:t-hom:20161016222456p:plain

さて、VBAでクラスモジュール「counter」を作ってみる。
このとき、Numberはクラスのパブリックフィールド、Plusはパブリックメソッドである。

Public Number As Long

Public Sub Plus(Optional n As Long = 1)
    Number = Number + n
End Sub

ちなみにJavaにはProperty構文がない。
だから基本的には普通のメソッドを使ってデータアクセスを制限する。

VBAではJavaと同じようにPropertyを使わずにデータアクセスを制限することもできる。
その場合は、このような書き方になる。

Private num As Long

Public Function GetNumber() As Long
    GetNumber = num
End Function

Public Sub LetNumber(n As Long)
    num = n
End Sub

Public Sub Plus(Optional n As Long = 1)
    num = num + n
End Sub

ここでnumはプライベートフィールド、GetNumberとLetNumberはメソッドである。
プライベートフィールドに間接的にアクセスするためのメソッドのことを「アクセサ」と呼ぶ。

メインコードでは、値の設定と取得で別のアクセサメソッドを呼び出す必要がある。

Sub hoge()
    Dim C As Counter
    Set C = New Counter
    C.LetNumber 10
    Debug.Print C.GetNumber
End Sub

ではVBAのPropertyは何かというと、このアクセサを作成するための専用構文なのである。
これを使うと遥かに便利になる。もう一度冒頭のコードを見てみよう。

■クラスモジュール「Counter」のコード

Private num As Long
Public Property Get Number() As Long
    Number = num
End Property

Public Property Let Number(n As Long)
    num = n
End Property

Public Sub Plus(Optional n As Long = 1)
    num = num + n
End Sub

■標準モジュールのコード

Sub hoge()
    Dim C As Counter
    Set C = New Counter
    C.Number = 10
    Debug.Print C.Number
End Sub

Javaではメソッドを通じてプライベートフィールドにアクセスするのだが、VBAのProperty構文ではあたかもパブリックフィールドに直接アクセスしているかのような感覚でアクセサを使用することができる。

内部的な動作はメソッドであるにもかかわらず、外からみたらパブリックフィールドのように扱えるもの。これがPropertyプロシージャである。
VBAの用語で言い直すと、動作はプロシージャ、外面は変数ってところか。

繰り返しになるが、C.Number = 10という文が行っているのは、NumberのLetプロパティーへ引数を渡す行為である。
カッコの代わりに、イコールで引数を渡せるようになっただけで、引数であることに変わりはない。

まとめ

Property…
フィールドのフリをしているが、そいつはメソッドだ!
気をつけろっ!

まぁJavaみたいに「フィールド・メソッド」という分類ではなく、ExcelVBAで広く説明されている「プロパティ・メソッド」という分類に従うと、Propertyは明らかにプロパティなんだけどね。
f:id:t-hom:20161017055812p:plain

ということで、今までどおりプロパティに代入という表現を使って良いと思うけれど、内部動作としてはプロシージャで、代入のフリをした引数渡しであることをしっかり押さえておくと混乱なく使いこなせるかなと思った次第。

最後に注意点として、Propertyはフィールドのように使われることが想定されるので、あまり重たい処理をプロパティの中で行うのは好ましくない。せいぜい書き込み時のチェックくらいにしておいたほうが良い。また今回メッセージボックスを表示させるサンプルも書いたけれど、あれはSubやFunctionと同じプロシージャであることを説明するためなので、普通はそんな使い方はしない。Propertyはあくまで「アクセサ」を実現するためのプロシージャなのだ。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。