Home » エクセルマクロ・Excel VBAの使い方 » オブジェクトの参照渡し・値渡し

対象:Excel 2010, Excel 2013, Windows版Excel 2016

引数の参照渡しについて記事を書きました。

この引数の渡し方に関して、単なるデータではなくオブジェクトの場合には、参照渡ししかできない、値渡しはできないと誤解している方がいらっしゃるようです。

オブジェクト変数が参照型変数であることから生じた誤解でしょうか。
あるいは、
配列変数が基本的には参照渡ししかできないため生まれた誤解でしょうか。

[スポンサードリンク]

結論からいえばオブジェクトは、参照渡しも値渡しもできます。

オブジェクトの参照渡しと値渡しを確認するサンプルマクロ

オブジェクトの場合も参照渡しも値渡しもできることは、簡単なプロシージャを作ってみれば、確認できます。

Public Sub オブジェクトの参照渡しと値渡しの確認()
 Dim sh_1 As Worksheet
 Set sh_1 = Sheets(1)

 Call オブジェクトの参照渡し(sh_1)
 Call オブジェクトの値渡し(sh_1)
End Sub

Private Sub オブジェクトの参照渡し(ByRef sh As Worksheet)
 MsgBox sh.Name & " (ByRef)"
End Sub

Private Sub オブジェクトの値渡し(ByVal sh As Worksheet)
 MsgBox sh.Name & " (ByVal)"
End Sub

上記のSubプロシージャ「オブジェクトの参照渡しと値渡しの確認」を実行すると、アクティブなブックの一番左のシート名が2回メッセージボックスに表示されます。

サンプルマクロの解説

「オブジェクトの参照渡しと値渡しの確認」では、オブジェクト変数に一番左のシートを表すWorksheetオブジェクトを、オブジェクト変数sh_1にセットして、
  Dim sh_1 As Worksheet
  Set sh_1 = Sheets(1)

別のSubプロシージャを呼んでいます。
  Call オブジェクトの参照渡し(sh_1)
  Call オブジェクトの値渡し(sh_1)

呼ばれたSubプロシージャでは、WorksheetオブジェクトのNameプロパティでシート名を取得して、メッセージボックスに表示しています。
  MsgBox sh.Name & " (ByRef)"
  MsgBox sh.Name & " (ByVal)"

もしも、オブジェクトの場合に値渡しができないのであったならば、
 Private Sub オブジェクトの値渡し(ByVal sh As Worksheet)
をコーディングする段階か、どんなに遅くとも実行時にはエラーが発生するはずですが、そんなことはありません。

オブジェクトは、参照渡しも値渡しもできます。

ObjPtr関数でオブジェクトのメモリアドレスを確認する

オブジェクトの値渡し・参照渡しは、メモリアドレスの様子を観察すると、理解しやすくなります。

むしろ、メモリアドレスを確認せずに、オブジェクトの値渡し・参照渡しを理解するのは、無理があるでしょう。

まず、各プロシージャの変数や引数が表しているWorksheetオブジェクトのメモリアドレスを確認しましょう。

先のプロシージャを以下のように変更して実行しましょう。

Public Sub オブジェクトの参照渡しと値渡しの確認()
 Dim sh_1 As Worksheet
 Set sh_1 = Sheets(1)
 Debug.Print ObjPtr(sh_1); " <- main(1)"

 Call オブジェクトの参照渡し(sh_1)
 Call オブジェクトの値渡し(sh_1)
 Debug.Print ObjPtr(sh_1); " <- main(2)"
End Sub

Private Sub オブジェクトの参照渡し(ByRef sh As Worksheet)
 MsgBox sh.Name & " (ByRef)"
 Debug.Print ObjPtr(sh); " <- ByRef"
End Sub

Private Sub オブジェクトの値渡し(ByVal sh As Worksheet)
 MsgBox sh.Name & " (ByVal)"
 Debug.Print ObjPtr(sh); " <- ByVal"
End Sub

呼び出すほうのSubプロシージャ「オブジェクトの参照渡しと値渡しの確認」には、
 Debug.Print ObjPtr(sh_1); " <- main(1)"
 Debug.Print ObjPtr(sh_1); " <- main(2)"
と、オブジェクト変数sh_1が参照している「Sheets(1)」シートのメモリドレスをイミディエイトウィンドウに出力する2行が、

呼ばれるほうのSubプロシージャには、
 Debug.Print ObjPtr(sh); " <- ByRef"
 Debug.Print ObjPtr(sh); " <- ByVal"
と、引数shで渡された、Worksheetオブジェクトのメモリアドレスをイミディエイトウィンドウに出力する行が追加されます。

私の環境で実行したときは、以下のようにメモリアドレスが出力されました。

ObjPtr関数の出力

参照渡しでも値渡しでも、まったく同じメモリアドレス「570016648」が並んでいます。

実際に出力される数字は、実行した環境やタイミングによってことなります。

もしも、オブジェクトの値渡しを行ったときに、オブジェクトそのものが値渡しされてコピーが作成されるのであるのならば、
  Debug.Print ObjPtr(sh); " <- ByVal"
で出力されるアドレスは、まったく別になるはずですが、そんなことはありません。

VarPtr関数で引数のメモリアドレスを確認する

値渡しを行ったときにも、オブジェクトのコピーが行われるのではないことがわかったら、オブジェクトの値渡し・参照渡しが、何の、値渡し・参照渡しなのかを確認しましょう。

先のプロシージャを以下のように変更して実行してください。

Public Sub オブジェクトの参照渡しと値渡しの確認()
 Dim sh_1 As Worksheet
 Set sh_1 = Sheets(1)
 Debug.Print VarPtr(sh_1); ObjPtr(sh_1); " <- main(1)"

 Call オブジェクトの参照渡し(sh_1)
 Call オブジェクトの値渡し(sh_1)
 Debug.Print VarPtr(sh_1); ObjPtr(sh_1); " <- main(2)"
End Sub

Private Sub オブジェクトの参照渡し(ByRef sh As Worksheet)
 MsgBox sh.Name & " (ByRef)"
 Debug.Print VarPtr(sh); ObjPtr(sh); " <- ByRef"
End Sub

Private Sub オブジェクトの値渡し(ByVal sh As Worksheet)
 MsgBox sh.Name & " (ByVal)"
 Debug.Print VarPtr(sh); ObjPtr(sh); " <- ByVal"
End Sub

私の環境で実行したときは、以下のようにメモリアドレスが出力されました。

VarPtr関数の出力

参照渡しの場合に引数のメモリアドレスが呼び出す側の「12316688」と同じであるのに対し、値渡しの場合には異なるメモリアドレス「12316504」となっています。

ByRef・ByValは、あくまでも引数が、参照渡しか値渡しかを指定しているわけです。

[スポンサードリンク]

Home » エクセルマクロ・Excel VBAの使い方 » オブジェクトの参照渡し・値渡し

「エクセルマクロ・Excel VBAの使い方」の記事一覧

検索


Copyright © インストラクターのネタ帳 All Rights Reserved.

.