えくせるちゅんちゅん

ことりがエクセルをちゅんちゅんするブログ

VBAでFunctionとProperty Getを使い分ける理由

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 が補完されるという前提知識とセットで覚えると理解が早いと思います。

よく見かけるのが、以下のような宣言ですね。

f:id:Kotori-ChunChun:20210307175348p:plain

最初から Public 番号 As Long で良いのでは?と思うかも知れませんが、一部の例外を除いてそれで問題ありません。( Public 変数 As Variant に 配列を代入している場合、呼び出し元で使用する際に配列全体のコピーが起きるため、書き込みしても反映されなくなってしまいます )


また、Getだけ、Setだけを実装することもできます。

特にGetのみ実装されるケースは多くて、読み取り専用の変数を作りたい時に使用されます。

そこで、ようやく本題の

Property Get と Function で何が違うの?

という疑問に答えます。

まずは、クラスモジュールに変数 f番号f名前 を実装して、この2つをイイ感じに連結して IDを生成する機能 Property Get RecordIDFunction 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 のほうが便利です。

それは何故なのか…こちらを御覧ください。

f:id:Kotori-ChunChun:20210307175411p:plain

1. アイコンが違う

オブジェクトブラウザやインテリセンスで表示されるアイコンが、 FunctionProperty Get で違います。

FunctionSub などと同じ緑色の処理っぽいアイコン。

Property Get変数 と同じ白色の紙に指差す情報っぽいアイコン。

とは言えアイコンなんて気にしない人もいるでしょう。私もその1人です。

でも、本当に重要なのは次です。

2. ローカルウィンドウに表示される

私が Property Get を使うのは…

ローカルウィンドウに表示されるから

に、他なりません。

上のローカルウィンドウを見て分かる通り、 FuncRecordID は載っていないのに、 RecordID は変数と同じように羅列されており、しかも値が表示されています。

ちなみに、ローカルウィンドウだけではなく、ウォッチウィンドウにも表示されます。

ただし、表示されるのは引数を持たないプロパティのみです。読み取り専用プロパティは、引数を付けたら実用上は関数と変わらないと思って良いでしょう。

引数付きプロパティについては後述します。

3. Callできない

Function は Call で呼び出せますが、 Property Get は呼び出せません。

f:id:Kotori-ChunChun:20210307175434p:plain

若干個人的な解釈を伴いますが、Functionの考え方は「処理を行う(何らかの変化を与える)」のが主目的で、「結果として何かを返す」のが副目的です。

一方でProperty Get の考え方は「何らかの情報を取得する」のが目的で、「取得に必要な計算を行う」というのは仕方がなくやっているという感じです。ココが変数との最大の違いです。

4. 値の設定にも同じプロシージャ名が使える

先の通りFunctionProperty 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

如何でしょうか。プロパティ のほうが、自然だと思いませんか?

プロパティを使った場合、読み書き共に同じメンバで使う事ができます。まるで変数のように。

以上が、 FunctionProperty Get の違いになります。


プロパティの注意

ここまでの流れで説明しなかったことがありますので補足します。

1. ブレークの度にプロパティが走る

プロパティは、ローカルウィンドウに表示されると書きましたが、それは逆に言えば表示を更新する度に Property Get の処理が実行されるということでもあります。

つまり、Stopで停止した時、実行時エラーになった時、F8でステップする度にプロパティに書いた処理が走ります。

試しに以下の様に Property Get の中に Debug.Print を書いてから、実行してみてください。

Property Get 名前()
    名前 = f名前
    Debug.Print "Property Get 実行"
End Property

f:id:Kotori-ChunChun:20210307175452g:plain

ローカルウィンドウの 名前 の更新が必要となるタイミングで、その都度 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. プロパティにも引数を持たせられる

滅多にお目にかかることはありませんが、プロパティにも複数の引数を持たせることが可能です。

例えば以下のプロパティ。

変数の代入で LetSet を使い分けなくても良いようにするというプロパティなのですが、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 にしてしまうと、呼び出し元の構文を直さないといけませんが、プロパティなら基本的に修正が不要というのが強みの一つ。

変数と同じようにローカルウィンドウにも表示されるから、デバッグがやりやすいというのがもう一つの理由です。

私が FunctionProperty Get を使い分ける理由は、そういうことなのです。

というか、変数が進化したらプロパティになって、Subが進化したらFunctionになるという感覚なので、使い分けようと考えること自体が無いと言ったほうが正しいのですが、人に説明するにあたって避けては通れないので比較してみました。

以上になりますが、プロパティを使わないとプログラムが書けないわけではないので、自分に合う使い方をされれば良いのではないかと思います。


他のプロパティの活用事例に関してはこちらを。

www.excel-chunchun.com

本文中で省略したクラスの初期値の設定に関してはこちらを。

www.excel-chunchun.com

以上、参考になれば幸いです。


何か御座いましたらコメント欄、またはTwitterからどうぞ♪

それではまた来週♪ ちゅんちゅん(・8・)

プライバシーポリシー