プロシージャ間の変数の受け渡し(その2)

より複雑な内容のマクロを作ろうとすると、プロシージャの分割が必要になります。
プロシージャの分割と、変数の受け渡しについての続きです。 プロシージャの分割と、変数の受け渡しについては、「変数の有効期間(範囲)、プロシージャ間の変数の受け渡し」で軽く説明しましたが、さらに掘り下げて説明しておく必要があります。
こちらでは、プロシージャ間の変数の受け渡しに絞って説明してみます。なお、モジュールレベル変数の利用については、「変数の有効期間(範囲)、プロシージャ間の変数の受け渡し」以上の説明が特にないので、ここでは触れません。



手始めはSubプロシージャ同士の変数の受け渡しです。
カッコ内に引数となる変数を宣言して、呼び元、呼び先ともに同じデータ型、個数を守って受け渡すことができます。

Option Explicit

' TEST2を呼んで結果を受け取りたい
Sub TEST1()
    Dim 変数 As Long

    変数 = 1
    Call TEST2(変数)
    MsgBox 変数
End Sub

' TEST1から呼び出されるサブプロシージャ
Private Sub TEST2(HENSU As Long)

    ' TEST1で宣言した「変数」に値をセット
    HENSU = 9
End Sub
このコードは、「変数の有効期間(範囲)、プロシージャ間の変数の受け渡し」3番目の項目で説明しているものです。
呼び先側プロシージャで単に変数を宣言しただけ場合は、「参照渡し(ByRef)として扱われ、変数名が呼び元、呼び先で違っていても同一の変数に対して参照・更新を行なうことになります。 これは呼び先でこの変数に書き込みを行なった場合は、呼び元でのセットした値が保持されないことを意味しており、知らずに使うと誤解の元です。
但し、これは良くない結果を呼ぶだけではありません。このことを逆手に使って、呼び先の結果を引数に宣言した変数を使って呼び元のプロシージャに伝えることができるのです。
なお、呼び先プロシージャにPrivateが付いているのは、このプロシージャが同一モジュール上の他のプロシージャから呼び出されるサブプロシージャであるためです。 時にメインで動作したり、単一モジュールを超えて呼び出される場合は、Privateは付けません。

呼び先で引数の値を変更されたくない場合はこうします。
呼び先の引数の宣言を「値渡し(ByVal)で宣言すると、呼び元でセットした変数は呼び先で違う値に置き換わることがなくなります。 具体的には、引数の変数宣言の前に、ByValを付加するだけです。

Option Explicit

' TEST2を呼んで結果を受け取りたい
Sub TEST1()
    Dim 変数 As Long

    変数 = 1
    Call TEST2(変数)
    MsgBox 変数
End Sub

' TEST1から呼び出されるサブプロシージャ
Private Sub TEST2(ByVal HENSU As Long)

    ' TEST1で宣言した「変数」に値をセット
    HENSU = 9
End Sub
重要なポイントです。この2つのマクロの動作結果が違うことがお解りでしょうか?
呼び元(上位側)プロシージャの記述で、呼び先で書き換わった変数を、それに気付かずに別処理で参照してしまうというミスはよくあることですが、 この「原理」を理解していないとテストを始めてから長時間悩むことになります。「正しく書いているのに、その通り動作しない」Excel側の問題のように捉える人もいるようです。
特にエラー発生箇所でその原因などを調べる時に、同じプロシージャ内でセットした変数が書き換わっていることに気が付かないことが多いので、デバッグ時にひとつひとつを確認することをしていれば このような現象にも短時間に対応できると思います。

結果を戻すことが要件なら、Functionプロシージャを使います。
Functionプロシージャなら、呼び元からは「関数」として利用することができます。

Option Explicit

' TEST2を呼んで結果を受け取りたい
Sub TEST1()
    Dim 変数 As Long
    Dim 変数2 As Long

    変数 = 1
    変数2 = TEST2(変数)
    MsgBox 変数 & " " & 変数2
End Sub

' TEST1から呼び出されるサブプロシージャ
Private Function TEST2(HENSU As Long) As Long

    ' TEST1で宣言した「変数」と戻り値に値をセット
    HENSU = 7
    TEST2 = 9
End Function
Functionプロシージャでは、プロシージャ名自身が戻り値の変数として機能します。呼び元ではこれを無視して、前出のSubプロシージャ同様にCallステートメントで呼び出すことも可能です。
もちろん、引数についてはSubプロシージャと全く同じで「値渡し(ByVal)」、「参照渡し(ByRef)を区別して処理することができます。

引数の利用有無を任意にするには、Optionalキーワードを使います。
下記は引数が2つあるサンプルで、2つ目にOptionalキーワードを付けています。

Option Explicit

' TEST2を呼んで結果を受け取りたい
Sub TEST1()
    Dim 変数 As Long

    変数 = 1
    Call TEST2(変数)
    MsgBox 変数
End Sub

' TEST1から呼び出されるサブプロシージャ
Private Sub TEST2(HENSU As Long, Optional OPT As Long)

    ' TEST1で宣言した「変数」に値をセット
    If OPT <> 0 Then
        HENSU = 9
    End If
End Sub
このコードは引数「OPT」には何もセットしていないので、データ型に応じて初期化された状態、つまり数値型では「ゼロ」になっています。
これと、下記は引数「OPT」に「1」をセットして呼び出しています。(「TEST2」は変更していません。)

Option Explicit

' TEST2を呼んで結果を受け取りたい
Sub TEST1()
    Dim 変数 As Long

    変数 = 1
    Call TEST2(変数, 1)
    MsgBox 変数
End Sub

' TEST1から呼び出されるサブプロシージャ
Private Sub TEST2(HENSU As Long, Optional OPT As Long)

    ' TEST1で宣言した「変数」に値をセット
    If OPT <> 0 Then
        HENSU = 9
    End If
End Sub
これらの動作結果の違いが分かるでしょうか。

このようにOptionalキーワードを付けた引数には呼び出し側で対応する値をセットしなくても良いことになるのですが、 引数が複数ある場合は名前ではなく左から順に位置で評価されるのでOptionalキーワードを付けた引数の右にOptionalキーワードを付けない引数があってはならないという規則があります。