Excel (VBA)

Excel VBAに関するフォーラムです。
  • 解決済みのトピックにはコメントできません。
このトピックは解決済みです。
質問

 
(Windows 7 Home Premium : Excel 2010)
ByRefの挙動について
投稿日時: 17/08/07 21:33:14
投稿者: おじいちゃん

何回か調べたのですが、答えが見つからず、諸先輩方のお知恵を拝借いたしたく、投稿します。
 
今回の疑問点は件名に書いていますが、「ByRef」の挙動です。
試したコードは下に示しました。
 
プロシージャ[piyo]はプロシージャ[hogera]を呼び出しています。
なお、プロシージャ[hogera]は引数を「ByRef」で渡しているため、参照渡しで引数を変更出来る認識でいました。
 
ケースA1,A3ではちゃんと変更されているにも関わらず、A2では変更されていません。
さらに全てのケースBでは変更されておらず、B1,B2,B3ではEmpty値になってしまいました。
同様に全てのケースCでは変更されておらず、C1,C2,C3ではEmpty値になってしまいました。
 
そもそもByRefであっても、引数を変更することはお行儀が悪いことも知っています。
(引数を変更するのではなく、本来はFunctionプロシージャでするべきことなのでしょうが。)
 
その上で、「ByRef」の挙動のみに着目して、このような違いが生まれるのかを教えていただければ幸いです。
 

Option Base 1
Option Explicit

Sub hogera(ByRef a As Variant)
    Dim buf(3)
    buf(1) = 5: buf(2) = 3: buf(3) = 7
    a = buf
End Sub

Sub piyo()
    Dim a
    a = 50: Stop            '50
    Call hogera(a): Stop    '5,3,7      -A1
    a = 50: Stop            '50
    hogera (a): Stop        '50         -A2
    a = 50: Stop            '50
    hogera a: Stop          '5,3,7      -A3

    Dim b(1)
    b(1) = 30: Stop         '30
    Call hogera(b): Stop    'Empty      -B1
    b(1) = 30: Stop         '30
    Call hogera(b()): Stop  'Empty      -B2
    b(1) = 30: Stop         '30
    hogera b: Stop          'Empty      -B3
    b(1) = 30: Stop         '30
    hogera (b): Stop        '30         -B4
    b(1) = 30: Stop         '30
    hogera (b()): Stop      '30         -B5

    Dim c(3)
    c(1) = 100: c(2) = 20: c(3) = 10: Stop  '100,20,10
    Call hogera(c): Stop                    'Empty      -C1
    c(1) = 100: c(2) = 20: c(3) = 10: Stop  '100,20,10
    Call hogera(c()): Stop                  'Empty      -C2
    c(1) = 100: c(2) = 20: c(3) = 10: Stop  '100,20,10
    hogera c: Stop                          'Empty      -C3
    c(1) = 100: c(2) = 20: c(3) = 10: Stop  '100,20,10
    hogera (c): Stop                        '100,20,10  -C4
    c(1) = 100: c(2) = 20: c(3) = 10: Stop  '100,20,10
    hogera (c()): Stop                      '100,20,10  -C5
End Sub

回答
投稿日時: 17/08/08 04:13:28
投稿者: simple

プロシージャhogeに変数を渡すとき、
hoge (c)
のように書くと、
カッコによる評価がいったん行われ、その結果が別の場所に書かれます。
そして、それが引数として渡されるので、元の c が変わることは一切ありません。
結果として値渡しのように見えます。
 
下記のMicrosoftの記事を参照してください。
https://msdn.microsoft.com/ja-jp/library/office/gg251769.aspx
 
-----------------
C1のケースで値が更新されないのは
Variant型変数の使い方によるのでしょう。
こんな風に書くと値の更新がされます。
 

Option Base 1

Sub hogera(ByRef a() As Long)
    Dim buf(3) As Long
    Dim k As Long
    
    buf(1) = 5: buf(2) = 3: buf(3) = 7
    For k = 1 To 3
        a(k) = buf(k)
    Next
End Sub

Sub piyo()
    Dim c(3) As Long
    
    c(1) = 100: c(2) = 20: c(3) = 10
    Call hogera(c)
    Stop  ' cは 5,3,7の値を持つものに変わります。
End Sub

投稿日時: 17/08/08 21:15:15
投稿者: おじいちゃん

simple様ご回答いただき、ありがとうございます。
 

引用:
下記のMicrosoftの記事を参照してください。
https://msdn.microsoft.com/ja-jp/library/office/gg251769.aspx

 
上記のURL非常に役に立ちました。
 
また、simple様のコードを少しいじってみました。
コードは[Code01]のとおりですが、[a]へ[buf]を代入しようとすると
「実行時エラー13 型が一致しません」のエラーが起きます。
イミディエイトウィンドウで確認し、型は一致していますが、エラーです。
 
これまででわかったことは以下の通りです。
(なぜそうなるのかの出典は探せませんでした。)
 
・型を厳密にしている場合、参照渡しで渡した引数を一気に変更しようとすると(a=buf)エラーが起きる。
・Variant型にしている場合で、配列を参照私の場合、エラーは起きないものの、Emptyとなってしまう。
・このことから、配列を引数で渡した場合、配列を一気に変更することは出来ない。
 
ここからが疑問なのですが、[Code02]のように単体の変数の参照渡し場合は、配列の参照渡しと違い、一気に変更しても、参照元の変数(ここでは引数元)が変更出来ている点です。
何故出来るのかわかる方がいらっしゃいましたら、教えていただけると幸いです。
 
[Code01]
Option Base 1

Sub hogera(ByRef a() As Long)
    Dim buf(3) As Long
    Dim k As Long
    
    buf(1) = 5: buf(2) = 3: buf(3) = 7
    a = buf          'ここで「実行時エラー13 型が一致しません」のエラー
'    For k = 1 To 3
'        a(k) = buf(k)
'    Next
End Sub

Sub piyo()
    Dim c(3) As Long
    c(1) = 100: c(2) = 20: c(3) = 10
    Call hogera(c)
    Stop  ' cは 5,3,7の値を持つものに変わります。
End Sub


[Immidiate]
イミディエイトウィンドウにて
- : a :  : Long(1 to 3)
    : a(1) : 100 : Long
    : a(2) : 20 : Long
    : a(3) : 10 : Long
- : buf :  : Long(1 to 3)
    : buf(1) : 5 : Long
    : buf(2) : 3 : Long
    : buf(3) : 7 : Long

[Code02]
Sub hogera(ByRef a As Variant)
    Dim buf(3)
    buf(1) = 5: buf(2) = 3: buf(3) = 7
    a = buf
End Sub

Sub piyo()
    Dim a
    a = 50: Stop            '50
    Call hogera(a): Stop    '5,3,7      -A1
End Sub

回答
投稿日時: 17/08/09 06:37:27
投稿者: simple

> 何故出来るのか
端的にいいますと、それは a がVariant型変数だからです。
 
「バリアント型変数を配列として使う」
http://officetanaka.net/excel/vba/variable/09.htm
を参考にして下さい。
 
もっとも、これは我々がよく使っている手法でもあるのです。

    Dim v
    v = Range("A1:B2").Value
などとすると思います。これとおなじことですよ。

回答
投稿日時: 17/08/09 13:34:17
投稿者: WinArrow
投稿者のウェブサイトに移動

>ByRef
を勉強のつもりで質問しているのでしたら、
余計なお世話になると思いますが・・・・
 
引数を持つプロシジャをつかう場合、
殆どが ByVal方式で記述するのではないかと思います。(これも私見です)
 
その中で、ByRef方式を使うと、メンテナンスを考えた時
混乱すると思います。
 
私は、配列を返すFunctionプロシジャの場合、
配列を返す方式でコーディングしています。
 
↓サンプルです。未テスト
 

Sub TEST()
Dim 変数1,変数2,i As Long
   Redim 変数2(1 To 2)
   変数2(1) = 50
   変数2(2) = 30
   変数1 = FuncTEST(変数2)
   If Not IsArray(変数1) Then
      Debug.Print 変数1
   Else
      For i = LBound(変数1) To UBound(変数1)
         Debug.Print 変数1(i)
      Next
   End If
End Sub
 
Function FuncTEST(ByVal 変数IN)
DIm 変数OUT,i As Long
    Redim 変数OUT(LBound(変数IN) To UBOUND(変数IN))
    For i = LBound(変数IN) To UBOUND(変数IN)
        変数OUT(i) = 変数IN(i) * 10
    Next
    FuncTEST = 変数OUT
End Function
 

投稿日時: 17/08/09 21:55:24
投稿者: おじいちゃん

simple様、たびたびの回答ありがとうございます。
 
改めて考えると、Variant型変数のなせる業ですね。
以下のpiyoの中のaはVariant型変数であり、
イメージで言うと「土地だけ確保した変数」のような感じですね。
土地だけ確保した状態で、上物は何でも来いの状態。
だからこそ、オブジェクトでも配列でも変数でも可能と。

Sub piyo()
    Dim a
    a = 50: Stop            '50
    Call hogera(a): Stop    '5,3,7      -A1
End Sub

一方で以下のpiyopiyoの中のcはVariant型としているが、
これは配列の「中身が」Variantなんですね。
イメージでは「土地の上に、3部屋あるマンションが既に立っている状態」。
中に入る人はなんでもいいけど、建物は既に立っている。
だから、いくらByRefで「c=buf」としても、建物は立っているわけだから
建物ごと変更することは出来ず、「Empty」になってしまうと。
Sub piyopiyo()
    Dim c(3) As Variant
    c(1) = 100: c(2) = 20: c(3) = 10
End Sub

イメージを含めて間違っていたら指摘していただけると嬉しいです。
個人的にはすっきりと解消できたと思っています。
 
WinArrow様、ご指摘ありがとうございます。
 
ByRef方式は迷宮入り可能性があることを十分に認識したうえで使用することを心がけています。
その上でByRef方式が適切だと判断した場合にのみ使用することとしています。
今回の例ですと、ByVal方式ですと値コピーが発生するため、
メモリ節約の観点からByRef方式でのコーディングを検討していました。
 
様々な方法を検討することが必要と思いますので、WinArrow様のお考えも
考慮しながら進めていきたいと思います。
 
また何かありましたら、コメントいただけると幸いです。

回答
投稿日時: 17/08/09 22:41:43
投稿者: simple

よろしいんじゃないかと思います。

投稿日時: 17/08/11 10:30:27
投稿者: おじいちゃん

本件は解決できたので、クローズとさせていただきます。
 
回答いただいたsimple様、WinArrow様、ありがとうございました。