【VBA】値渡しと参照渡しについて調べてみる

#はじめに
JavaScriptではオブジェクトは参照渡しで代入されるそうです。
(以前の記事)[【Vue.js】オブジェクトを代入するときの注意点](https://qiita.com/hiro_underclass/items/57714fdcde31e67beb82)
VBAではどうなのか調べてみました。
#Collectionオブジェクト

```vb
Sub Test()

    Dim obj1 As Collection
    Set obj1 = New Collection

    obj1.Add "item1"

    Dim obj2 As Collection
    Set obj2 = obj1

    obj2.Add "item2"

    Debug.Print obj1.Count, obj2.Count
    '=> 2    2

End Sub
```

`obj2`に`item2`を追加したところ、`obj1`も要素数が2個になりました。
オブジェクトは参照渡しで代入されるということですね。

プロシージャの引数について調べてみます。

```vb
Sub Test()

    Dim obj1 As Collection
    Set obj1 = New Collection

    obj1.Add "item1"

    Dim obj2 As Collection
    Set obj2 = obj1

    obj2.Add "item2"

    Debug.Print obj1.Count, obj2.Count
    '=> 2    2

    '続き
    Call Test2(obj2)

    Debug.Print obj1.Count, obj2.Count
    '=> 3    3

End Sub

Sub Test2(ByVal obj As Collection)

    obj.Add "item3"

End Sub
```

`test2`プロシージャ実行後、`obj2`の要素数が3になったので、`ByVal`キーワードを使用しても参照渡しになることがわかりました。
(たぶん、オブジェクトのアドレスが値渡しされているのだと思います)

本題とは関係ないですが・・・

```vb
Sub Test()

    Dim obj1 As Collection
    Set obj1 = New Collection

    obj1.Add "item1"

    Dim obj2 As Collection
    Set obj2 = obj1

    Set obj1 = Nothing

    Debug.Print obj2.Count
    '=> 1

End Sub
```

`Set obj1 = Nothing`としても`obj2`は生き残っています。
オブジェクト変数`obj1`による、オブジェクトへの参照が破棄されただけで、オブジェクト自体が削除されたわけではないということですね。
#Rangeオブジェクト

```vb
Sub Test()

    Dim rng1 As Range
    Set rng1 = Range("A1")

    rng1.Value = 123

    Dim rng2 As Range
    Set rng2 = rng1

    rng1.Value = "abc"

    Debug.Print rng1.Value, rng2.Value
    '=> abc    abc

End Sub
```

こう書くと、オブジェクトは参照渡しで代入されることが一目瞭然ですね。
代入すると新しいオブジェクトができるわけではなく、同じオブジェクトを別の変数がそれぞれ参照しているだけなんですね。
#配列
配列について試してみたところ、本題と関係ないことが色々わかりました。

```vb
Sub Test()

    Dim arr1(0) As Long
    arr1(0) = 1

    Dim arr2(0) As Long
    arr2 = arr1
    '=> エラー

End Sub
```

エラーが出ました。
[配列に割り当てることができません](https://docs.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/can-t-assign-to-an-array)
配列は代入できないそうです。

```vb
Sub Test()

    Dim arr1(0) As Long
    arr1(0) = 1

    Dim arr2 As Variant
    arr2 = arr1

    arr2(0) = 2

    Debug.Print arr1(0), arr2(0)
    '=> 1    2

End Sub
```

`Variant`型の変数に配列を代入することができました。
`arr2`を変更しても`arr1`が変更されなかったので、値渡しのようです。
「配列は値渡し『的に』代入される」といった感じでしょうか。あまり適切な表現ではないかもしれません。

プロシージャの引数について調べてみます。

```vb
Sub Test()

    Dim arr1(0) As Long
    arr1(0) = 1

    Dim arr2 As Variant
    arr2 = arr1

    arr2(0) = 2

    Call Test2(arr1)

    Debug.Print arr1(0), arr2(0)
    '=> 3    2

End Sub

Sub Test2(arr() As Long)

    arr(0) = 3

End Sub
```

配列を引数とした場合、`ByVal`キーワードは使用できず、参照渡しになります。

```vb
Sub Test()

    Dim arr1(0) As Long
    arr1(0) = 1

    Dim arr2 As Variant
    arr2 = arr1

    arr2(0) = 2

    Call Test2(arr1)

    Debug.Print arr1(0), arr2(0)
    '=> 1    2

    Call Test3(arr1)

    Debug.Print arr1(0), arr2(0)
    '=> 3    2

End Sub

Sub Test2(ByVal arr As Variant)

    arr(0) = 3

End Sub

Sub Test3(ByRef arr As Variant)

    arr(0) = 3

End Sub
```

`Varinat`型の引数に配列を渡すこともできます。
こうすると、`ByVal`キーワードを使って配列を値渡しで渡すことができます。

```vb
Sub Test()

    Dim arr1(0) As Long
    arr1(0) = 1

    Dim arr2 As Variant
    arr2 = arr1

    arr2(0) = 2

    Call Test2(arr1(0))

    Debug.Print arr1(0), arr2(0)
    '=> 3    2

End Sub

Sub Test2(ParamArray arr() As Variant)

    arr(0) = 3

End Sub
```

`ParamArray`キーワードを使うと、参照渡しとなります。
引数は`Variant`型にする必要があります。
ちなみに、`ParamArray`は、複数の値を受け取ってを個の配列にとりまとめるもので、配列を受け取るわけではありません。
したがって、`Call Test2(arr1)`とすると、配列`arr1`を要素する新たな配列が作られ、これが引数となります。
#おわりに
値渡しと参照渡しを混同したバグが発生したら、原因調査が難しそうです。
そうならないためにも、これらをきちんと使い分けられるようになりたいです。