引数の値渡しと参照渡し

中級VBA

引数の値渡しと参照渡し

 Excel VBA のプロシージャ(関数)では引数の扱いとして、値渡し(ByVal)参照渡し(ByRef)を指定する事ができます。
 プロシージャに引数を取る時、変数で渡す場合が多いと思いますが、
 値渡し」(ByVal)とは変数の中身の値を渡す方法。
 参照渡し」(ByRef)とは、変数自体を渡す方法。
 になります。

 値渡し」の方は引数の変数の値を渡すだけなので変数の持つ値は変わりません。
それに対して、参照渡し」とは、変数という容器を渡すようなイメージなのでプロシージャ内で値が変化すれば変数値も変化します。
 「値渡し」と「参照渡し」の違いは、プロシージャを呼び出した後に、変数の値が変化するかしないかですが、この事はその関数を呼び出した後に関わってきます。

値渡し(ByVal)

 値渡しでは、プロシージャに引数を渡した後で、引数を参照しても値は変化しません。
 つまり変数 a は 1 のまま変化しません。
 ただし、呼び出し先(ProcVal)の内部では、引数の値( n )を変更すれば変化しています。
 
 値渡しにしたい場合は、引数に「ByVal」を付けます。

 (これは当たり前のように感じると思います。)

Sub 値渡し()
    Dim a As Long
    a = 1
    ProcVal a
    
    Debug.Print "値渡し a=" & a
End Sub

Sub ProcVal(ByVal n As Long)
    n = n + 1
    Debug.Print "ProcVal内部 n=" & n
End Sub

【結果】
ProcVal内部 n=2
値渡し a=1

 参照渡しにしたため、呼び出し元では、変数 a が変化していません。

参照渡し(ByRef)

 参照渡しでは、変数自体を渡すイメージなので、プロシージャ内で引数の値を変更すると参照の際も値が変化します。
 つまり変数 a は呼び出し先(ProcVal)の内部での計算を受けて 2 に変化します。
 これは、変数 a 自体を渡したためで、呼び出し先(ProcVal)の内部での計算結果が引数の値( n )と同様に変化します。
 
 参照渡しにしたい場合は、引数に「ByRef」を付けます。

Sub 参照渡し()
    Dim a As Long
    a = 1
    ProcRef a
    
    Debug.Print "参照渡し a=" & a
End Sub

Sub ProcRef(ByRef n As Long)
    n = n + 1
    Debug.Print "ProcRef内部 n=" & n
End Sub

【結果】
ProcRef内部 n=2
参照渡し a=2

 参照渡しを使えば、SubプロシージャでもFunctionのように値を返すような事ができるようになります。
 ただし、少々わかりずらくなるので、変数名を明示的にするなどの工夫が必要になると思います。

ByVal や ByRef を付けない場合

 引数に ByVal や ByRef を付けないデフォルトの状態はどうなるでしょうか? 実験してみましょう。

Sub test()
    Dim a As Long
    a = 1
    Proc a
    Debug.Print "test a=" & a
End Sub

Sub Proc(n As Long)
    n = n + 1    '引数 n を変更した。
    Debug.Print "Proc n=" & n
End Sub

【結果】
Proc n=2
test a=2

 結果を見ると、呼び出し元の test() では 1 でしたが、呼び出した先で 2 に変更され、呼び出し元の引数だった a は 2 になり、参照渡し」(ByRef)になっているのがわかると思います。
 つまり気を付けていないと、引数に渡した変数は、呼び出し先で値が変更されてしまうかもしれないという事になります。

勝手に値を変更されないようにするには

 以下のようなコードを書いてしまうと、カウント用の変数が書き換えられてしまうため上手く動作しません。

'このコードは無限ループ!
Sub testCode()
    Dim i As Long
    For i = 0 To 10
        resetNum i
        
        Debug.Print i
        Stop        '一応ここで止まる
    Next i
End Sub

Sub resetNum(n As Long)
    n = 0
End Sub

 このプログラムでは、resetNum() の引数が参照渡しになっているため、呼び出し元の Forループ内部で、 i を0に書き換えてしまうため無限ループになっています。
 これを防ぐには、以下のように ByVal として値渡しにしてやります。

Sub resetNum(ByVal n As Long)
    n = 0
End Sub

引数の渡し方で注意する点

 ExcelVBAのプロシーシャの引数は何も付けない(デフォルト)状態だと参照渡しとなり、引数の変数値を変更すると呼び出し元でも値が変化してしまいます。
上記のようなトラブルを防ぐには以下のような点に注意しましょう。

・参照渡しをしたくない場合は、その引数の変数を左辺に置かない
・ByVal や ByRef を明示的に使って意図をはっきりとさせる

 参照渡しに関しては、扱いを間違えるとバグの原因になり、エラーとならない事も多いので発見が困難になることもあります。プロシージャで引数を使う場合は、上記の2点に気を付けるようにしましょう。

タイトルとURLをコピーしました