■ 参照方法、値渡しと参照渡しを明示しよう!
初級編でも解説した値渡しと参照渡し、ここではもう少し深く掘り下げて考えてみたいと思います。
VBAの大原則に、関数を呼び出すとき
「戻り値を取得するときはカッコをつけ、戻り値を取得しないときにはカッコをつけない」
というのがあります。
たとえば、次のMsgBox関数の使用例をみてください。
MsgBox "test" … 正
MsgBox ("test") … 誤
どちらもコンパイルエラーにならずに「test」を表示しますが、VBAの文法として正しいのは上の表記です。
MsgBox "test", vbCritical … 正
MsgBox ("test",vbCritical) … 誤(コンパイルエラー)
にすれば、下の表記がコンパイルエラーになるのがお分かりいただけると思います。
なぜエラーになったのかというと、下の表記のカッコは引数を渡すためではなく演算をするためのカッコになっているからです。
MsgBox (1 + 2), vbCritical … 正
MsgBox (1 + 2,vbCritical) … 誤(コンパイルエラー)
こう書いたほうがわかりやすいでしょう。上のMsgBoxは「3」を表示しますが、下は当然コンパイルエラーになります。
MsgBox ("test")を、引数として渡すカッコで記述するには、
a = MsgBox("test") … 正
Call MsgBox("test") … 正
このように記述します。
さてVBAで引数を渡すときのデフォルトは参照渡しですが、引数を括弧で囲むと強制的に値渡しになるといわれています。
本当でしょうか?
Sub test()
Dim MyStr As String
MyStr = "test"
func MyStr
Debug.Print MyStr
End Sub
Sub func(pMyStr As String)
pMyStr = pMyStr & pMyStr
Debug.Print pMyStr
End Sub
funcプロシージャにMyStrが渡されます。渡された時点ではMyStrには"test"が格納されています。
Debug.Print pMyStr では"testtest"がプリントされます。呼び出し元のプロシージャに制御が返り、
Debug.Print MyStr で"testtest"がプリントされました。つまり参照渡しになっています。
func MyStr の部分をfunc (MyStr) に変更してみます。実行すると結果は…
"testtest"
"test"
が出力されました。確かに値渡しになってますね…。
では、引数を増やしてみましょう。
Sub test()
Dim MyStr1 As String
Dim MyStr2 As String
MyStr1 = "test1"
MyStr2 = "test2"
func (MyStr1, MyStr2)
Debug.Print MyStr1 & MyStr2
End Sub
Sub func(pMyStr1 As String, pMyStr2 As String)
pMyStr1 = pMyStr1 & pMyStr1
pMyStr2 = pMyStr2 & pMyStr2
Debug.Print pMyStr1 & pMyStr2
End Sub
ありゃりゃ?func (MyStr1, MyStr2) のところでコンパイルエラーになってしまいましたね。
ということは…これは先ほどと同じで引数を渡すための括弧ではなく演算をするための括弧だったんですね。
引数が一つのときに値渡しされたのは、括弧で囲まれた引数を式として評価するために一時的にメモリの別領域に値が確保されたためです。
引数として渡したのはあくまで別に確保された領域のポインタを参照渡ししただけで、元のデータの場所ではありません。
参照先でデータが書き換えられても、元のデータが書き変わらなかったのはこのためです。
しかしこれは正しいVBAの使用法ではありません。
ためしにCallステートメントをつけてみましょう。括弧がついていても参照渡しをしますね。
Call func(MyStr1, MyStr2)
しかしこんなことでは、気をつけないとコーディング時にエラーが大量発生してしまいます。
いったいなにがいけなかったのでしょう?
それは、参照先のプロシージャで引数にByVal(値渡し)、ByRef(参照渡し)キーワードをつかって明示的に引数の参照法を指定していなかったことです。
値渡しにしたければ、引数をカッコで囲むのではなくByValで明示的に値渡しをすべきです。
そしてVBAの文法に則り、返り値を利用しないのであれば引数にカッコをつけないか、Callステートメントを利用して引数をカッコで囲むべきです。
局所的に変数を利用していると、こういった引数の参照が頻繁に行われます。
面倒でもByVal、ByRefキーワードを使用し、返り値を利用しない関数・プロシージャの呼び出しには、Callステートメントを使用するようにしましょう。
たったそれだけの手間をかけるだけでバグの発生率が大きく減少し、システムの保守性が大幅に向上します。
|