はじめに
前回のFunctionプロシージャの解説で、引数の説明の中で「参照渡し」と「値渡し」のサンプルを使いましたが「参照渡し」と「値渡し」ってわかりますか?
ByRef(参照渡し)は変数そのものを渡し、ByVal(値渡し)は値のコピーを渡します。 そのため、呼ばれた側で引数を変更すると、ByRefは呼び出し元の変数も変わり、ByValは変わりません。

引数にByRefなんて書いたことがないんですが、関係ありますか?(^^;
くるみこ書いていなくても、実はByRefとして動いているんです。実際に動かしながら確認しましょう(^^)/
【この記事でわかることは】次の内容を実行結果付きで解説します。
- ByRefとByValの動作の違い
- 指定を省略したときに起こりやすい不具合
- 使い分けの基準
- 配列やオブジェクトを渡すときの注意点
- 「ByRef 引数の型が一致しません」の対処法
前回記事のおさらいは、下のカードをクリックすれば開きます(^^ゞ


前回記事は、Functionプロシージャの使い方に関する解説記事でした。覗いてみてね(^^)/
ByRefとByValの違い
まず、違いを表で確認します。
| 項目 | ByRef(参照渡し) | ByVal(値渡し) |
|---|---|---|
| 渡すもの | 変数そのもの | 値のコピー |
| 呼ばれた側で変更すると | 呼び出し元の変数も変わる | 呼び出し元の変数は変わらない |
| 指定を省略したとき | こちらになる | 明示が必要 |
| 主な用途 | 呼び出し元の値を変更したいとき | 値を受け取って使うだけのとき |

動作を確認するサンプル
「参照渡し」「値渡し」という言葉だけで考えると、渡す側に違いがあるように感じてしまいます。しかし、通常は引数を渡す側を見ても判別できません。でも、引数を受け取る側のプロシージャの定義(引数宣言)を見れば判別できます。
では、以下の簡単なサンプルを使って確認してみましょう。
'参照渡しと値渡しの確認
Sub ByRefValSample()
Dim a As String
a = "くるみこ" '変数に文字列"くるみこ"を入れる
'プロシージャByRefSampleの引数に変数を渡して呼び出す
Call ByRefSample(a)
MsgBox a & " 参照渡し(ByRef)" '変数の値を表示する
'プロシージャByValSampleの引数に変数を渡して呼び出す
Call ByValSample(a)
MsgBox a & " 値渡し(ByVal)" '変数の値を表示する
End Sub
'参照渡し
Sub ByRefSample(ByRef a As String)
a = a & "ちゃんはかわいい!" '変数に文字列を追加
End Sub
'値渡し
Sub ByValSample(ByVal a As String)
a = "kurumicoだよ!" '変数に文字列を代入
End Sub参照渡しの結果は、次のとおりです。

変数を渡したプロシージャで変数に文字列”ちゃんはかわいい!”を追加すると、呼び出し元の変数も変更されました。
値渡しの結果は、次のとおりです。

値渡しでも変数に「kurumicoだよ!」を代入していますが、呼び出し元の変数には反映されていないことが確認できました。
指定を省略するとByRefになる
ByRef も ByVal も書かなかった引数は、ByRefとして扱われます。次の税込金額を求めるコードは、この動作が原因で2回目の結果がおかしくなります。
Sub TaxBug()
Dim price As Long
price = 1000
Debug.Print "1回目: " & AddTax(price) ' 1回目: 1100
Debug.Print "2回目: " & AddTax(price) ' 2回目: 1210
End Sub
Function AddTax(amount As Long) As Long
amount = amount * 1.1
AddTax = amount
End Function同じ1,000円を渡しているつもりでも、1回目の処理で price が1,100に変わるので、2回目は1,100円に税が加算されます。エラーは出ないため、結果を見るまで気づきにくい不具合です。
引数を ByVal にすると、呼び出し元の price は変わらなくなります。
Function AddTax(ByVal amount As Long) As Long
amount = amount * 1.1
AddTax = amount
End Function
エラーが出ないのに結果だけ変わるなんて、気づくのが大変そうです(^^;
くるみこそうなんです。引数を書き換える処理では、ByRefとByValのどちらなのかを意識しましょう(^^)
ByRefとByValの使い分け
判断の基準は、呼び出し元の変数を変更したいかどうかです。
| やりたいこと | 使うもの |
|---|---|
| 値を受け取って使うだけ | ByVal |
| 結果を1つ返す | Functionの戻り値 |
| 結果を2つ以上返す | ByRef |
| 配列を渡す | ByRef(後述) |
ByValでは値をコピーしますが、数値や短い文字列であれば、コピーの負担を気にする必要はほとんどありません。処理速度より、意図しない書き換えを防ぐことを優先するのがおすすめです。
Functionの戻り値の書き方は、Excel VBA Functionプロシージャを使ってみようで解説しています。
ByRefで複数の値を返す
Functionの戻り値は基本的に1つなので、複数の結果を返したいときはByRefが便利です。次のサンプルは、割り算の商と余りを同時に受け取ります。
Sub DivideSample()
Dim q As Long
Dim r As Long
Divide 17, 5, q, r
Debug.Print "商: " & q & " 余り: " & r ' 商: 3 余り: 2
End Sub
Sub Divide(ByVal a As Long, ByVal b As Long, _
ByRef quotient As Long, ByRef remainder As Long)
quotient = a \ b
remainder = a Mod b
End Sub入力用の引数をByVal、結果を返す引数をByRefと書き分けると、どの引数が変更されるのかがコードから読み取れます。
配列を渡すときはByRefで受け取る
配列の変数を () 付きの引数で受け取るときは、ByRefしか指定できません。ByVal values() As Long と書くと、コンパイルエラーになります。
Sub ArraySample()
Dim nums(1 To 3) As Long
nums(1) = 1
nums(2) = 2
nums(3) = 3
DoubleAll nums
Debug.Print nums(1) & ", " & nums(2) & ", " & nums(3) ' 2, 4, 6
End Sub
Sub DoubleAll(ByRef values() As Long)
Dim i As Long
For i = LBound(values) To UBound(values)
values(i) = values(i) * 2
Next i
End Sub元の配列を変更したくない場合は、ByVal のVariant型で受け取ります。配列全体がコピーされるので、大きな配列では処理時間とメモリが増える点に注意してください。
Sub ArrayCopySample()
Dim nums(1 To 3) As Long
nums(1) = 1
ChangeCopy nums
Debug.Print nums(1) ' 1(元の配列は変わらない)
End Sub
Sub ChangeCopy(ByVal values As Variant)
values(1) = 100
End Sub配列の基本は、Excel VBA 配列とは?基礎的使い方をサンプルで詳しく解説をご覧ください。
オブジェクトをByValで渡すとどうなるか
RangeやCollectionなどのオブジェクトをByValで渡しても、オブジェクトの中身は呼び出し元と共有されたままです。コピーされるのは、オブジェクトそのものではなく、オブジェクトを指す参照だからです。

Sub ObjectSample()
Dim items As Collection
Set items = New Collection
AddItem items
Debug.Print "追加後: " & items.Count ' 追加後: 1
ReplaceByVal items
Debug.Print "ByValで差し替え: " & items.Count ' ByValで差し替え: 1
ReplaceByRef items
Debug.Print "ByRefで差し替え: " & items.Count ' ByRefで差し替え: 0
End Sub
Sub AddItem(ByVal c As Collection)
c.Add "A" ' 中身の変更は呼び出し元に反映される
End Sub
Sub ReplaceByVal(ByVal c As Collection)
Set c = New Collection ' 差し替えは呼び出し元に反映されない
End Sub
Sub ReplaceByRef(ByRef c As Collection)
Set c = New Collection ' 差し替えも呼び出し元に反映される
End Sub違いが出るのは、Set で別のオブジェクトに差し替えたときだけです。ByValでも、セルの値の変更やCollectionへの追加は、そのまま呼び出し元に反映されます。

ByValなら中身も守られると思っていました(^^;
くるみこくるみこ:オブジェクトでは間違えやすいところです。ByValで守られるのは変数の参照だけ、と覚えておきましょう(^^)/
呼び出し側の括弧で値渡しになる
ByRefの引数でも、呼び出すときに変数を括弧で囲むと、値のコピーが渡されます。
Sub ParenSample()
Dim a As String
a = "くるみこ"
ByRefSample (a) ' a は変わらない
Call ByRefSample((a)) ' a は変わらない
Debug.Print a ' くるみこ
ByRefSample a ' a が変わる
Debug.Print a ' くるみこちゃんはかわいい!
End SubByRefSample は、最初のサンプルで作成したプロシージャです。
ByRefSample (a) のように、プロシージャ名と括弧の間に空白があるコードは、括弧内が式として評価されます。式の結果は一時的な値なので、呼び出し元の a には影響しません。意図せずこの書き方になると、ByRefなのに値が戻らない原因になります。
よくある質問・エラー対処
Q. 「ByRef 引数の型が一致しません」と表示される場合は?
→ ByRefの引数と、渡した変数の型が一致していないことが原因です。特に多いのが、次のように1行で複数の変数を宣言している場合です。
Sub MismatchSample()
Dim a, b As Long ' a はVariant型、b はLong型になる
a = 1
b = 2
ShowValue a ' ここでコンパイルエラーになる
End Sub
Sub ShowValue(ByRef x As Long)
Debug.Print x
End SubDim a As Long, b As Long のように変数ごとに型を指定すると解消します。呼ばれた側で値を変更しないなら、引数を ByVal x As Long に変更しても解消します。

Q. ByRefの引数に数値や計算式を直接渡せますか?
→ 渡せます。Foo 10 や Foo n + 1 は、一時的な値が渡されるだけなので、呼び出し元の変数には影響しません。
Q. 文字列や長い配列は、ByRefのほうが速いですか?
→ コピーが発生しない分、ByRefのほうが有利です。ただし、文字列を1回渡す程度なら差はほとんど感じられません。大量のループや大きな配列で問題になったときに、ByRefへの変更を検討してください。
Q. Publicのグローバル変数とByRefは、どちらを使うべきですか?
→ 引数で渡すByRefをおすすめします。グローバル変数はどこからでも変更できるので、値が変わった原因を追いにくくなります。
まとめ(おわりに)
まとめと感想など
くるみこ「値渡し」と「参照渡し」については理解できましたか?

今まで何も書かずに、全部ByRefで使っていたことがわかりました(^^;
くるみここれからは、引数ごとにByRefとByValを選んでみてくださいね(^^)/
【今回分かったことは】
- ByRefは変数そのものを渡し、ByValは値のコピーを渡す
- 指定を省略するとByRefになり、意図しない書き換えの原因になる
- 基本はByValを使い、呼び出し元の値を変更したいときだけByRefを使う
- 配列の変数を
()付きで受け取るときはByRefのみ - オブジェクトはByValでも中身が共有される
- 呼び出し側で変数を括弧で囲むと、ByRefでも値渡しになる
関連記事
- Excel VBA Functionプロシージャを使ってみよう — 戻り値の書き方と引数の基本
- Excel VBA 配列とは?基礎的使い方をサンプルで詳しく解説 — 配列の宣言と動的配列の基本
- ExcelマクロVBA 変数を使ってみよう(初心者向け10) — 変数の宣言と適用範囲の基本
今後の記事について
今回の記事はいかがだったでしょうか。皆さまのお役に立てたなら幸いです(^^;
「汎用でだれでも使えて活用できるように考えてVBAを使う」というポリシーで、記事を継続して書いていきたいと思っています。どうぞよろしくお願いしますm(_ _)m
【検討中の今後の記事内容は・・・・・】
・実務に役立つものを提供できるよう常に検討しています(^^ゞ
・その他雑記的に「プチネタなど」もいろいろ考えていきたいと思います・・・・・
・今後の記事にもご期待ください(^^)/
※1回買えば複数サイトで使い回しOK
記事のサンプルファイルをダウンロードできます
今回の記事のサンプルはこのリンク先からダウンロードできるようにしています!
1つのブックで両方を試せるよう、ByValに修正した例は関数名を AddTaxFixed、実行用のマクロ名を TaxFixed にしています。MismatchSample はコンパイルエラーになる例なので、コメントとして収録しています。
過去の記事で使用したサンプルファイルがダウンロードできるページを設置しています
こちら(このリンク先)からご利用ください


