少し抽象的に聞こえる言葉だけど、実際は「中身のコピーを渡すのか」「そのものを渡すのか」という違いです。VBAでは引数の前に ByVal/ByRef を付けて制御します。
基本の考え方
- ByVal(値渡し):
引数の「値のコピー」を渡します。中で値を変えても、呼び出し元の変数は変わりません。 - ByRef(参照渡し):
変数そのものを渡します。中で値を変えると、呼び出し元の変数も変わります。
動作の違いをコードで理解する
ByValの例(呼び出し元は変わらない)
Sub IncreaseByVal(ByVal n As Integer)
n = n + 1
End Sub
Sub TestByVal()
Dim x As Integer
x = 10
IncreaseByVal x
MsgBox x ' → 10(変わらない)
End Sub
VBByRefの例(呼び出し元が変わる)
Sub IncreaseByRef(ByRef n As Integer)
n = n + 1
End Sub
Sub TestByRef()
Dim x As Integer
x = 10
IncreaseByRef x
MsgBox x ' → 11(変更が反映される)
End Sub
VB型ごとのポイント
- 数値・文字列:
ByValで渡すと安全。ByRefは「中で編集して呼び元にも反映したい」ときに使う。 - 配列:
VBAの配列は参照型の性質があり、ByRefで渡すと要素変更が呼び元に反映されます。配列の再割り当て(ReDim)やサイズ変更の可否もByVal/ByRefで挙動が変わるため注意。
Sub FillArray(ByRef a() As Integer)
a(1) = 100 ' 呼び元にも反映
End Sub
VB- オブジェクト(Range, Worksheet, Collectionなど):
参照そのものを扱います。ByValでも「参照のコピー」を渡すので、参照先のプロパティ変更は呼び元に見えます。ただし、参照の付け替えはByRefでないと呼び元に反映されません。
Sub SetValueByVal(ByVal rng As Range)
rng.Value = "OK" ' 参照先のセルは更新される
Set rng = rng.Offset(1, 0) ' 呼び元の参照は変わらない
End Sub
Sub SetValueByRef(ByRef rng As Range)
rng.Value = "OK"
Set rng = rng.Offset(1, 0) ' 呼び元の参照もずれる
End Sub
VB使い分けの指針
- 安全第一でByVal:
ラッパー関数やユーティリティ処理では、意図せぬ副作用を避けるために基本はByVal。 - 複数の結果を返したいならByRef:
戻り値が1つに限られるFunctionの代わりに、出力用引数をByRefで受け取る。
Sub CalcStats(ByVal a As Integer, ByVal b As Integer, ByRef sum As Integer, ByRef avg As Double)
sum = a + b
avg = (a + b) / 2#
End Sub
VB- パフォーマンスを気にする場合:
大きな配列や文字列を大量に渡すときは、ByRefで不要なコピーを避けると高速なことが多い。ただし副作用管理が必須。 - APIやイベントハンドラ:
Excel/VBAのイベントや外部API宣言では、仕様に合わせてByVal/ByRefが固定のことがあるので、その通りに書く。
よくある落とし穴と対策
- 思わぬ副作用:
ByRefで渡した変数が、プロシージャ内部の変更で書き換わり、バグの原因に。
→ 対策: 明確に「出力用」「入力用」を分け、出力用だけByRefにする。変数名に役割を示す。 - オブジェクトの参照の付け替え:
ByValでRangeを渡すと、Set rng = ...で呼び元の参照は変わらない。
→ 参照自体を更新したいときはByRefを使う。 - OptionalとByRefの組み合わせ:
Optionalは既定値が必要で、ByRefでは既定値を設定できないケースがあるため、必要ならVariant + IsMissingや適切な既定値のByValを検討。
まとめの一言
- ByVal: 入力用。中で変えても呼び元は変わらない。安全で予測可能。
- ByRef: 出力・更新用。中で変えたら呼び元も変わる。強力だが副作用に注意。
迷ったら「基本はByVal、必要なときだけByRef」。それがスッキリしたVBAの第一歩です。


