VBAの Function と Property Get は非常に似ています。
検索して出てくる説明では、両者を何故使い分けているのか、今ひとつシックリこない人が多いと思います。
そこで、私がなぜプロパティを使い分けているのか説明します。
プロパティとは
プロパティとは何か、強引に一言で説明すると「超すごい変数」です。
なぜなら、皆さんはすでに、変数もプロパティも意識せずに使ってます。
'変数vを宣言 Dim v As Variant '変数vにA1セルのValueプロパティの値を代入 v = Range("A1").Value 'A1セルのValueプロパティに5を代入 Range("A1").Value = 5
決まりがあるわけではありませんが、 オブジェクト名.プロパティ名
と言う形で使用することが多いでしょう。
別の言い方をするなら
- 自律性の持った変数 とか
- 変数の出入り口
なんて呼び方もできます。
(別言語ですが、C#では、プロパティを実装する get / set のことを「アクセサ」と呼びます。変数に到達するためのコードだからアクセサー…この方がシックリ来ますね)
VBAでプロパティを自作するためには、以下3つのプロシージャを使います。
Property Get Property Set Property Let
このうち、 SetとLetは値を代入される時に実行されるコードを書くのに使用し、Getは値を取り出される時に実行されるコードに使用します。
SetとLetの違いについては省略しますが、オブジェクト変数の代入で記載する Set
と深い関係があります。Setと書かないときは Let
が補完されるという前提知識とセットで覚えると理解が早いと思います。
よく見かけるのが、以下のような宣言ですね。
最初から Public 番号 As Long
で良いのでは?と思うかも知れませんが、一部の例外を除いてそれで問題ありません。( Public 変数 As Variant
に 配列を代入している場合、呼び出し元で使用する際に配列全体のコピーが起きるため、書き込みしても反映されなくなってしまいます )
また、Getだけ、Setだけを実装することもできます。
特にGetのみ実装されるケースは多くて、読み取り専用の変数を作りたい時に使用されます。
そこで、ようやく本題の
Property Get と Function で何が違うの?
という疑問に答えます。
まずは、クラスモジュールに変数 f番号
と f名前
を実装して、この2つをイイ感じに連結して IDを生成する機能 Property Get RecordID
と Function FuncRecordID
を作ります。
Init
は、初期値をまとめて設定するためのプロシージャで、クラスを使うなら作成しておいたほうが便利ですが、今回は関係ないので無視してください。
'clsRecord クラス Private f番号 As Long Private f名前 As String Sub Init(p番号, p名前) f番号 = p番号 f名前 = p名前 End Sub Property Get RecordID() As String RecordID = Format(f番号, "0000") & "_" & f名前 End Property Function FuncRecordID() As String FuncRecordID = Format(f番号, "0000") & "_" & f名前 End Function
呼び出し側の標準モジュールはこんな感じにしておきます。
Sub Test_Class() Dim rec As New clsRecord rec.Init 3, "ちゅん" Debug.Print rec.RecordID = "0003_ちゅん" Debug.Print rec.FuncRecordID = "0003_ちゅん" End Sub
どちらも同じように使えて、同じ結果になります。
True True
しかし、このような処理では Property Get
のほうが便利です。
それは何故なのか…こちらを御覧ください。
1. アイコンが違う
オブジェクトブラウザやインテリセンスで表示されるアイコンが、 Function
と Property Get
で違います。
Function
は Sub
などと同じ緑色の処理っぽいアイコン。
Property Get
は 変数
と同じ白色の紙に指差す情報っぽいアイコン。
とは言えアイコンなんて気にしない人もいるでしょう。私もその1人です。
でも、本当に重要なのは次です。
2. ローカルウィンドウに表示される
私が Property Get
を使うのは…
ローカルウィンドウに表示されるから
に、他なりません。
上のローカルウィンドウを見て分かる通り、 FuncRecordID
は載っていないのに、 RecordID
は変数と同じように羅列されており、しかも値が表示されています。
ちなみに、ローカルウィンドウだけではなく、ウォッチウィンドウにも表示されます。
ただし、表示されるのは引数を持たないプロパティのみです。読み取り専用プロパティは、引数を付けたら実用上は関数と変わらないと思って良いでしょう。
引数付きプロパティについては後述します。
3. Callできない
Function は Call で呼び出せますが、 Property Get
は呼び出せません。
若干個人的な解釈を伴いますが、Functionの考え方は「処理を行う(何らかの変化を与える)」のが主目的で、「結果として何かを返す」のが副目的です。
一方でProperty Get の考え方は「何らかの情報を取得する」のが目的で、「取得に必要な計算を行う」というのは仕方がなくやっているという感じです。ココが変数との最大の違いです。
4. 値の設定にも同じプロシージャ名が使える
先の通りFunction
も Property Get
も、経緯はどうあれ処理を行い結果を取得する点では同じです。
そこで逆の事をしたい……情報を格納したい場合は、別のプロシージャを書くのが普通です。
「f名前
には、半角大文字に統一して格納する」という仕様にした場合に、今まで同様にプロパティを使う場合と使わない場合で書いてみます。
Rem プロパティで名前を読み書き Rem 名前は必ず半角大文字 Property Get 名前() 名前 = f名前 End Property Rem Property Letでは、イコールの後に書いた値が引数となる Property Let 名前(v) f名前 = StrConv(v, vbNarrow + vbUpperCase) End Property Rem プロパティを使わずに読み書き Function Func名前() Func名前 = f名前 End Function Sub Set名前(v) f名前 = StrConv(v, vbNarrow + vbUpperCase) End Sub
すると、使うときには以下の様に書くことができます。
'標準モジュール Sub Test_名前を読み書き() Dim rec As New clsRecord rec.名前 = "ちゅん" Debug.Print rec.名前 rec.Set名前 "ちゅん" Debug.Print rec.Func名前 End Sub
如何でしょうか。プロパティ のほうが、自然だと思いませんか?
プロパティを使った場合、読み書き共に同じメンバで使う事ができます。まるで変数のように。
以上が、 Function
と Property Get
の違いになります。
プロパティの注意
ここまでの流れで説明しなかったことがありますので補足します。
1. ブレークの度にプロパティが走る
プロパティは、ローカルウィンドウに表示されると書きましたが、それは逆に言えば表示を更新する度に Property Get
の処理が実行されるということでもあります。
つまり、Stopで停止した時、実行時エラーになった時、F8でステップする度にプロパティに書いた処理が走ります。
試しに以下の様に Property Get
の中に Debug.Print
を書いてから、実行してみてください。
Property Get 名前() 名前 = f名前 Debug.Print "Property Get 実行" End Property
ローカルウィンドウの 名前
の更新が必要となるタイミングで、その都度 Property Get
が実行されていることを観測できます。
もし仮に Property Get
で情報の書き換えを行っている場合、デバッグ中のみバグを引き起こすという罠にハマります。
- カウント変数を増やしている
- ログやイミディエイトへの書き出しを行っている
- 数秒かかるような非常に重たい処理を書いている
- よくわからない別の関数を呼んでいる
- 初回呼び出し時のみ初期値を設定するような特別な処理を行っている
と言った場合には、痛い目を見る前に見直してください。そういった処理は Function のほうが向いてます。
最後のは isInit
のようなフラグで実行を拒否して、コンストラクタ(の代用関数)で初期化しなければならという仕組みにしたほうが安全です。
2. 別にクラスを使わなくても良い
今回の説明では、実用性を感じやすいようにクラスモジュールにプロパティを実装しましたが、標準モジュールに書くこともできます。
ただ、今回のようなモジュール変数を書き換えるという目的では、あまりメリットを感にくいです。(モジュール変数に直接アクセスできてしまうので、アクセス経路が2つできると使う時に混乱します)
代わりと言ってはなんですが、個人的に CreateObject の代用で多様しています。
(wshがローカルウィンドウで見れると美味しいか?と言われると微妙ですが。)
Property Get wsh() Set wsh = CreateObject("WScript.Shell") End Property
これをグローバル変数にしてしまうと、初期値の代入をどこかでしなければならなくなるので、関数やプロパティにしておいたほうが便利なのです。
Public wsh As Object Public Sub wshInit() Set wsh = CreateObject("WScript.Shell") End Sub
余談ですが、参照設定しているオブジェクトで、事前バインディングができる場合は、自動Newしたほうが便利です。
Public fso As New FileSystemObject
ただし状態(変数)を持たない、機能のみを備えたクラスに限りますが。
3. プロパティにも引数を持たせられる
滅多にお目にかかることはありませんが、プロパティにも複数の引数を持たせることが可能です。
例えば以下のプロパティ。
変数の代入で Let
と Set
を使い分けなくても良いようにするというプロパティなのですが、1つ目の引数が書き込み先の変数、2つ目の引数が書き込みたい値となります。
Rem 変数に値を代入 Rem Rem @creator https://qiita.com/nukie_53/items/bde16afd9a6ca789949d Rem Rem @param outVariable 出力先変数 Rem @param inExpression 書き込み内容 Rem Rem @example SetVar(out) = in Rem Public Property Let SetVar(outVariable As Variant, inExpression As Variant) If VBA.IsObject(inExpression) Then Set outVariable = inExpression ElseIf VBA.VarType(inExpression) = vbDataObject Then Set outVariable = inExpression Else Let outVariable = inExpression End If End Property
普通のSubで実装したら
SetVar 変数名, 値
と書く所が、プロパティなら
SerVar(変数名) = 値
と書けるようになります。
もともと、やろうとしていた式は
Set 変数名 = 値 Let 変数名 = 値
の自動的な使い分けなので、プロパティを使った構文のほうが自然な書き方になります。
※もちろん、賛否両論あるでしょうが。
もっとたくさんの引数を書いた場合は、こんなイメージになります。
プロパティ名(引数1,引数2,引数3,...) = 最後の引数
使う機会は、まず無いと思いますが記憶の片隅にでもどうぞ。
まとめ
補足が長くなりましたが、つまりプロパティは変数を拡張したようなモノなのです。
最初は変数 Public 名前 As string
と書いてコーディングして、ちょっとした処理を書き加えたいときと思った時にプロパティに置き換えるくらいの気持ちで使えば大丈夫です。
そこで置き換える時にFunction にしてしまうと、呼び出し元の構文を直さないといけませんが、プロパティなら基本的に修正が不要というのが強みの一つ。
変数と同じようにローカルウィンドウにも表示されるから、デバッグがやりやすいというのがもう一つの理由です。
私が Function
と Property Get
を使い分ける理由は、そういうことなのです。
というか、変数が進化したらプロパティになって、Subが進化したらFunctionになるという感覚なので、使い分けようと考えること自体が無いと言ったほうが正しいのですが、人に説明するにあたって避けては通れないので比較してみました。
以上になりますが、プロパティを使わないとプログラムが書けないわけではないので、自分に合う使い方をされれば良いのではないかと思います。
他のプロパティの活用事例に関してはこちらを。
本文中で省略したクラスの初期値の設定に関してはこちらを。
以上、参考になれば幸いです。
何か御座いましたらコメント欄、またはTwitterからどうぞ♪
それではまた来週♪ ちゅんちゅん(・8・)