VBA関数呼び出しのByValとByRefを整理する

VBA関数呼び出しのByValとByRefを整理する

VBAの引数渡しを、値そのものではなく「変数の入れ物」と「参照先」の違いから理解する

Copyright © 2026 LWP 山中 一弘

本資料は、出典を明記いただければ、商用・非商用を問わず、ご自由に複製・改変・再配布していただけます。なお、著作権表示は改変せず、そのまま記載してご利用くださいますようお願いいたします。

記事要約

 VBAでは、プロシージャや関数に引数を渡すとき、ByVal と ByRef を使い分けます。説明としては「値渡し」と「参照渡し」と言われますが、この言い方だけでは、オブジェクト変数、配列、Variant、括弧付き引数の挙動が分かりにくくなります。

 この記事では、過去の実験メモをもとに、VBAの引数渡しを「変数の入れ物を渡すのか」「入れ物の中身をコピーして渡すのか」という観点で整理します。特に、値型変数、オブジェクト変数、Variant、括弧付き引数の違いを、実務で誤解しやすいポイントに絞って説明します。

 結論から言えば、ByRef は呼び出し元の変数そのものに別名を付けて扱うイメージに近く、ByVal は呼び出し先に別の仮引数変数を用意するイメージです。ただし、オブジェクト変数では「変数そのもの」と「変数が指しているオブジェクト」を分けて考えないと、結果を取り違えます。

本記事の対象とゴール

想定読者

  • VBAで ByVal と ByRef の違いを説明できるようになりたい人

  • オブジェクト変数を ByVal で渡しても中身が変更される理由を整理したい人

  • Variantを ByRef で受けたときの例外的な挙動に引っかかったことがある人

  • 括弧付き引数がなぜ値渡しのように見えるのかを理解したい人

本記事で得られること

  1. ByVal と ByRef を、単なる用語ではなく変数の扱いとして説明できます。

  2. オブジェクト変数の ByVal と ByRef の違いを、参照先オブジェクトと変数の再代入に分けて判断できます。

  3. 括弧付き引数が「引数指定を変える」のではなく「式を評価して一時的な値を作る」と理解できます。

本記事で扱わないこと

  • VBA VMやCOM VARIANTの内部仕様の厳密な説明

  • VarPtr、ObjPtr、StrPtr の網羅的な使い方

  • すべての型に対する実験結果の再検証

  • 業務標準としての引数設計規約の完全版

先に結論

 VBAの ByVal と ByRef は、「呼び出し先で仮引数という変数をどう用意するか」の違いとして見ると整理しやすくなります。

 ByVal では、呼び出し先に別の仮引数変数が用意されます。値型であれば値がコピーされます。オブジェクト変数であれば、オブジェクトそのものではなく、オブジェクトを指す参照値がコピーされます。

 ByRef では、呼び出し元の変数と呼び出し先の仮引数が、同じ入れ物を別名で見ている状態に近くなります。そのため、呼び出し先で仮引数に代入すると、呼び出し元の変数にも影響します。

 ここで重要なのは、オブジェクト変数を渡すときです。ByVal で渡しても、変数が指しているオブジェクトに対する操作は同じオブジェクトへ届きます。一方で、呼び出し先で仮引数に別のオブジェクトを Set しても、ByVal なら呼び出し元の変数は差し替わりません。ByRef なら差し替わります。

第1章(値型変数のByValとByRef)

 最初に、整数や日付、文字列のような値型変数で考えます。ここでは、変数の入れ物と値のコピーが比較的分かりやすく見えます。

1.1 ByValは値をコピーして渡す

 ByVal で値型変数を渡すと、呼び出し先には別の仮引数変数が用意されます。呼び出し元の変数と、呼び出し先の仮引数は別の入れ物です。

 そのため、呼び出し先で仮引数を書き換えても、呼び出し元の変数は変わりません。型の暗黙変換が可能な場合、呼び出し時に変換された値が渡されることもあります。

1.2 ByRefは同じ変数を別名で扱う

 ByRef で値型変数を渡すと、呼び出し先の仮引数は、呼び出し元の変数を別名で扱うような状態になります。

 このとき、呼び出し先で仮引数を書き換えると、呼び出し元の変数も変わります。実験メモでは、VarPtr で見たときに、呼び出し元の変数と仮引数のアドレスが一致することを根拠に、この理解を置いていました。

 ただし、VarPtr を使った観察はVBA内部仕様の公式説明ではありません。この記事では、厳密なVM仕様ではなく、実務上の理解モデルとして扱います。

第2章(オブジェクト変数で混乱しやすい理由)

 オブジェクト変数では、値型変数よりも混乱しやすくなります。理由は、変数そのものと、変数が指しているオブジェクトを分けて考える必要があるからです。

2.1 オブジェクト変数の中身は参照値である

 オブジェクト変数には、オブジェクトそのものが入っているわけではありません。オブジェクトを指す参照値が入っています。

 そのため、オブジェクト変数を ByVal で渡すと、参照値がコピーされます。呼び出し元の変数と呼び出し先の仮引数は別の変数ですが、どちらも同じオブジェクトを指しています。

 この状態で、呼び出し先からオブジェクトのプロパティを変更したり、メソッドを呼んだりすると、同じオブジェクトに対する操作になります。したがって、ByVal で渡したのに変更が見える、ということが起きます。

2.2 ByValで変わらないのは変数の差し替えである

 ByVal でオブジェクト変数を受け取った場合、呼び出し先の仮引数に別のオブジェクトを Set しても、呼び出し元の変数は差し替わりません。

 これは、コピーされた参照値を持つ別の仮引数変数を書き換えているだけだからです。オブジェクトの中身に対する操作と、変数がどのオブジェクトを指すかの差し替えは別の話です。

2.3 ByRefなら変数の差し替えも伝わる

 ByRef でオブジェクト変数を渡すと、呼び出し先の仮引数は呼び出し元の変数を別名で扱う状態に近くなります。

 そのため、呼び出し先で仮引数に別のオブジェクトを Set すると、呼び出し元の変数も別のオブジェクトを指すようになります。

 実務では、この差が重要です。オブジェクトの状態を変更したいだけなら ByVal でも届きます。呼び出し元の変数が指すオブジェクト自体を差し替えたいなら、ByRef が必要になります。

第3章(Variantが例外に見える理由)

 元記事では、Variant型の仮引数を使う場合に、通常の型一致ルールだけでは説明しにくい挙動があると整理していました。

3.1 ByRefでは型一致が基本になる

 ByRef は呼び出し元の変数そのものを別名で扱うため、基本的には型が一致している必要があります。呼び出し先が違う型の入れ物として扱うことはできないからです。

 たとえば、整数型の変数を、文字列型の ByRef 仮引数として渡すことはできません。同じ入れ物を別の型として扱うことになるためです。

3.2 Variantは受け皿として特殊に見える

 一方で、仮引数がVariantの場合、実引数が整数型などでも ByRef で受けられる場面があります。元記事では、Variantが実際の値だけでなく、値へのポインタを持てる構造であることを手がかりに、この挙動を説明していました。

 実務上は、Variantを ByRef の受け口にすると、型の見え方が一段複雑になります。呼び出し先でVariant仮引数を書き換えると、呼び出し元の値に影響する場合があります。

 この挙動は便利な反面、読み手にとって分かりにくくなります。業務コードでは、Variantを汎用的な受け口として使うときほど、何を変更する可能性があるのかをコメントやプロシージャ名で明示した方が安全です。

第4章(括弧付き引数は何をしているのか)

 VBAでは、引数に括弧を付けると値渡しになる、という説明を見かけることがあります。これは結果としてそう見える場合がありますが、理解としては少し雑です。

4.1 括弧は引数指定を変えるのではなく式を評価する

 括弧付きの引数は、ByVal や ByRef の指定そのものを変えているというより、式として評価された結果を渡していると考える方が整理しやすくなります。

 たとえば、変数 n をそのまま渡す場合と、(n) のように括弧付きで渡す場合では、後者は式の評価結果として一時的な値が作られます。

 その一時的な値には、呼び出し元の変数名がありません。したがって、ByRef で元の変数を書き換えるような扱いができず、値渡しのように見えます。

4.2 変数と式を区別する

 この理解で大事なのは、変数と式を区別することです。

 変数は、名前を持った入れ物です。式は、評価されて値になります。括弧付き引数は、変数そのものを渡すのではなく、式として評価された値を渡す場面を作ります。

 元記事では、VarPtr(n) と VarPtr((n)) を比較することで、括弧付きの値が元の変数とは違うものとして扱われることを確認していました。

第5章(実務での判断基準)

 ここまでを、実務でどう使うかに落とします。内部構造を完全に知ることより、どの場面でどちらを選ぶべきかを決められることが重要です。

5.1 迷ったらByValを基本にする

 呼び出し先で、呼び出し元の変数そのものを書き換える必要がないなら、ByVal を基本にします。

 値型変数であれば、呼び出し元の値を守れます。オブジェクト変数であっても、呼び出し元の変数が別のオブジェクトへ差し替わることは避けられます。

 ただし、オブジェクト変数を ByVal で渡しても、オブジェクト自体の状態変更は可能です。ここを誤解してはいけません。

5.2 ByRefは変更する意思を示す

 ByRef を使うときは、呼び出し先が呼び出し元の変数を変更する可能性がある、という意思表示になります。

 値型変数なら値が変わります。オブジェクト変数なら、変数が指すオブジェクトを差し替えることもできます。

 そのため、ByRef を使うプロシージャでは、名前やコメントで「何を変更するのか」を明確にした方が安全です。

5.3 実験コードは結論だけを残す

 元記事には、VarPtr を使った実験コードが多く残っていました。しかし、公開記事としては、そのまま長いコードを貼るより、観察結果と判断基準を本文に落とした方が読みやすくなります。

 読者が再実験したい場合にだけ、最小限のコード例を別途用意すれば十分です。この記事では、コードの全量転載ではなく、実験から得た理解を記事として整理する方を優先しました。

まとめ

 VBAの ByVal と ByRef は、「値渡し」「参照渡し」という言葉だけで覚えると、オブジェクト変数やVariant、括弧付き引数で混乱しやすくなります。

 実務では、ByVal は呼び出し先に別の仮引数変数を用意する、ByRef は呼び出し元の変数を別名で扱う、と考えると整理しやすくなります。

 オブジェクト変数では、変数の差し替えと、変数が指すオブジェクトへの操作を分けて考えます。ByVal でもオブジェクトの状態変更は届きますが、呼び出し元の変数の Set 先は差し替わりません。ByRef なら差し替わります。

 括弧付き引数については、引数指定が変わるというより、式として評価された一時的な値を渡していると考えると理解しやすくなります。

 結論として、VBAの引数渡しは、変数の入れ物、参照値、参照先オブジェクトを分けて見ることが重要です。この分け方ができると、ByVal と ByRef の選択を、暗記ではなく設計判断として扱えるようになります。

元記事

 この記事は、Dropbox Paperに残っていた過去記事「VBA 関数呼び出しにおける参照型変数と値型変数の実験」(2019年4月21日)を原型として、LWP公開記事用に再構成したものです。

 取得日: 2026年5月14日

 元記事URL: https://www.dropbox.com/scl/fi/9kfan39i0gcwjqvh2hnno/VBA.paper?rlkey=m8vk0ck6s5eh24xfbvee282oi&dl=0