見出し画像

引数を括弧で囲うとByRefなのにByValになるって本当?

こんにちわ。
Twitterやり始めたばかりにこんな天気のいい日に部屋に引きこもってくだらないブログを書いている。まぁこんな人生も良し。なんじゃそりゃ。

さて今回はVBAのCallとByRef/ByValの関係についてまとめる。
ただ・・タイトルから迷った。なぜならこれらは全く関係ない機能であるからだ。関係ないはずなのに、あたかも関係があるかのように語られていることがあり、私としてはモヤモヤしているのだ。
というわけで、今回はこのモヤモヤが取れればOK。
自己満足記事なので、最後まで読んで「ちっ」と舌打ちしても自己責任でよろぴこぴこ。

まとめ

・Callステートメントを使うときは()は省略不可
・Callステートメントを省略する場合は()も省略必須
・ByRef引数に()付けたらByValになるなんて大間違い
  →たまたまそれっぽい動きに見えるだけ
・初心者にお勧めするプロシージャ呼び出し記述方法

Callステートメント

まずはCallステートメント(VBAのプロシージャー呼び出し)の基礎・・
これについてはOffice TANAKA先生の記事を参照。されたい

http://officetanaka.net/excel/vba/statement/Call.htm

画像1

この記事でもまとめ通り、Call省略+()付きはエラー、Call記述+()省略でもエラーとある。

ところが実際やってみるとエラーになるのは、下図通りCall記述+()省略の場合のみ。Call省略+()付きはエラーにならない。TANAKA先生記事ではエラーになるとあるのに!? 弘法も筆の誤り?サルも木から落ちる?河童の川流れ?toto先生もダジャレスベる?(あっ最後のはそんなに珍しくなかった)

兎に角、混乱の元がここにある。

画像4


勘の良い方のために引数が2つの場合のサンプルもここで記載する。
引数が2つの場合は、TANAKA先生記事の通りCall省略+()付きもコンパイルエラーになる。

画像4

さてこれはどういうことか??

Callを省略したときの記述ルール

Docsには以下の通り記述されている。
要約すると
Callを記述する場合は引数を括弧で囲まなければならない。
と書かれている。mustなので()を必ず使えという意味である。

画像4

では、Callを省略した場合は?
それは同じDocs内のRemarksに記述されている。
要約すると、こんな感じ。
Callを書かねーなら、括弧も書くんじゃねーよ!
いかん。育ちが悪いのがバレる・・・。

画像5

ここでまた混乱。んじゃこれはなぜエラーにならないの?
これは疑念はこのブログを最後まで読むと解決される。

画像6

引数 ByRef/ByValのお話

さて、ここでいきなり話が変わる。引数ByRef/ByValのお話。
長くなるので基礎的な話は省略。

画像8

ByRefの動きはこんな感じ。
みんなが共用して見られるホワイトボードaに呼び出し側が1と書いて、myFuncに指示を出す。myFuncはホワイトボードを2に書き換えて、終わったことを告げる。呼び出し側はホワイトボードaが2になっていることを認識できる。という仕組み。
つまり共有して見られるホワイトボードaを指さしてしているところがポイント。

画像9

ここで、真ん中の行を次のように改造する。
変更前:myFunc3 a
変更後:myFunc3 a+5
すると・・あれれ?
さっきまでmyFuncの後はa=2だったのに、今は1のまま。

画像9

これは、呼び出し側はホワイトボードにあるaを直接指ささず、a+5=6という別の情報(ここではホワイトボード??)に書かれた情報をmyFuncに渡して実行させているとうこと。myFuncはByRefで受け取った引数をしっかり書き換えているのだが、その書き換えたのはホワイトボード??であって、ホワイトボードaではないということ。
ホワイトボードaだけを見ている呼び出し側はByRefではなく、あたかもByValで呼び出されているような動作に見えてしまうぽよん。

画像17

つまり、引数に計算式を直接記述した場合は、myFunc側でいくらByRefで書き換えられようとも呼び出し側では、その結果を確認する術がないとい。

計算式ってなに?

ごめ。厳密な定義はしりませんw
今回のケースでいうと、四則演算子や論理演算子、文字列式などです。算術式の一つとして括弧があります。

・・・やっときたぁぁぁぁぁ括弧!!待ってたぜぇ。

計算式は a+5 でもいいし、(a+5)*3 でもいい、((a+5)*3)^2でもいい。計算できればなんでもOK。

んじゃここで問題。a=1の時、
(a)=?
((a))=?
(((a)))=?

全部 1です。括弧付ける意味ある?って聞きたい人いるよね?
そんなの知らんがな。括弧があれば、括弧内を優先して計算するという算術式の決まりがある。それに従って計算するだけよ。

さぁ、ここまで読んでくれたあなたはわかったかな?
引数を括弧付きで渡すと、ホワイトボード??を経由した計算結果が渡されるため、myFuncの結果はホワイトボードa(呼び出し元の変数)に影響しない。つまりByValっぽい動きになるのです。
いや、myFuncはByRefなので、もらったホワイトボード書き換えてるのよ。呼び出し側がそれを見てないだけなの。
なんか努力が報われないmyFuncかわいそう。

画像11


Call付きとCall省略の見た目の違い

Callを省略する場合、プロシージャー名(myFunc3)と引数の間に半角スペースをいれなければならない。じゃないと文字繋がって何かいているのか判別不能。

画像12

一方Callを記述する場合は、プロシージャー名myFunc3直後に括弧、変数を記述できる。

画像13

では、混乱のもととなっている、Call省略+()付きの記述をよーく見てみてみよう。
インデントは今回のテーマがわかりやすくなるようにわざとずらしてます。

画像14

おやや? Call省略の場合は、プロシージャー名の後にスペースが入ってますね。これは私が意図的に入力したのではなく、勝手に記述されます。
つまりCall省略ケースでは、引数は「a」ではなく「(a)」であるということ。この括弧はCallステートメントに紐づく括弧ではなく、あくまで計算式の括弧なのですよ。先の通り、引数が計算式の場合はmyFuncの動作結果は、呼び出し元の変数aには影響しません。
「括弧で囲うとByValになる」という都市伝説的な動作説明が流布しちゃったのも理解できますが、実際の動作とちょっと違う。myFuncは健気にByRefのお仕事をしているのですよ。ByValになるなって言われたらmyFuncが可哀想。

引数が1つと2つ以上でエラーか否かが異なる理由

もうわかりますよね。
Callを省略した場合、
引数が1つの時は、()が計算式の一部として認識されますが、
引数が2つ以上ある場合は、計算式として成り立ちません。(a,a)なんて計算ができないからです。なのでこれはエラーになります。

画像16

初心者にお勧めする記述方法

初心者の方にお勧めする書き方は以下2つです。
Callもしくは戻り値=は必ず書く! ()も必ず書く!
引数が一つもないような関数、メソッドを実行する場合など、明らかにCallを省略しても支障がない!と言い切れる自信がついてからにしましょう。よくわからないうちは、この2パターンにしたほうが選択肢が少なくて安全です。

画像17


最後に

まぁ、しゃあ専用ザクだって「通常の3倍!」なんて言われてますが、実のところリミッター外して連続稼働時間犠牲にてやっと1.3倍ってのが限界。
分かりやすい噂って広がりやすいですからね。みなさんも変な噂に惑わされないように注意してください。

画像15


最後までありがとうございました。

この記事が気に入ったらサポートをしてみませんか?