以前にこのようなツイをしたところ、諸先輩方から色々なアドバイスを頂きました。
某書の写経です。
— Kou Excelが大好きだ! (@LoverExcel) 2021年9月23日
このコードでCollectionに追加した「Studentクラス」の中身が更新されて、「Studentクラス」を破棄した時にCollectionに追加した「Studentクラス」は影響を受けないことが両立するのでしょうか?
mItems.Add vStudentがEnd with下なら自分的には納得できるのですが。#助けてVBA pic.twitter.com/hHirs98fKO
下記ツイのツリーにも記載しましたが、改めて自分がモヤモヤと勘違いしていた点と、教えて頂いてこうだろうと思って点をまとめてみたいと思います。
先日は下記悩みに色々とアドバイスを頂きましてありがとうございます。
— Kou Excelが大好きだ! (@LoverExcel) 2021年9月26日
複数の方から図解してみると良いとアドバイスを頂きましたので、当初勘違いしてモヤモヤしていたこと、アドバイスを元に修正したイメージを拙いながらまとめてみようと思います。 https://t.co/B86PdFcPJx
ひょっとすると私と同じ様な勘違いをしている人もいるかも知れません。
今回は当初私の頭の中にあった図を紹介いたします。
当初の私の勘違いですので明らかに間違っていますのでご注意ください
オブジェクト変数での疑問点
下記は「Excel VBA 実践のための技術」からの抜粋です。
Public Sub Add(ByVal vID As String, _ ByVal vName As String, _ ByVal vAge As Long, _ ByVal vMobile As String) Dim vStudent As Student 'Studentはユーザー定義クラス mItemDictionary.Add Key:=vID, item:=mItems.Count + 1 Set vStudent = New Student '↓↓↓↓↓ここから mItems.Add vStudent 'Collection.addです With vStudent .ID = vID .Name = vName .Age = vAge .Mobile = vMobile End With Set vStudent = Nothing '↑↑↑↑↑ここまで End Sub
疑問点は上記コード内の「ここから」~「ここまで」のコードです。
Collectionに追加したStudentクラスの中身が更新されて(with句内の処理)、
Studentクラスを破棄した時(set vStudent = nothing)に、
Collection内のStudentクラスが影響を受けない事がわからない。
[勘違い版]検証用コード
ここから上記コードをどのように勘違いしていたからわからなかったのか記載いたします。
検証用にもう少し簡略化して全体を見れるコードを用意しました。
Studentクラス
'### Studentクラス Private mName As String Public Property Let Name(ByVal vName As String) mName = vName End Property Public Property Get Name() As String Name = mName End Property
標準モジュール
Sub オブジェクト参照テスト() Dim 名簿Col As Collection Set 名簿Col = New Collection Dim 生徒A As Student Set 生徒A = New Student 名簿Col.Add 生徒A 生徒A.Name = "Kou" Debug.Print 名簿Col(1).Name Set 生徒A = Nothing Debug.Print 名簿Col(1).Name End Sub
上記のコードを実行するとイミディエイトウィンドウに「Kou」が2回表示されます。
2回目は直前で生徒Aを破棄しているのになぜ「Kou」と表示されるのか。
[勘違い版]オブジェクト型変数の生成
「set 生徒A = New Student」というコードで「生徒A」という名前の新しいStudentオブジェクトが作成される。
※Studentはユーザー定義クラスです
[勘違い版]オブジェクト変数を渡す
VBAの変数の渡し方にはByVal型とByRef型があります。
- ByVal型 (値渡し) ・・・変数の「値」を渡す
- ByRef型 (参照渡し)・・・変数そのものを渡す
値渡し的イメージ
このコードを実行すると「Kou」とイミディエイトウィンドウに表示されます。
値渡し的なイメージでオブジェクト変数を渡す、つまり変数をコピーして新しい変数がCollectionオブジェクト内に生成されるはず。
そうすると変数「生徒A」とCollectionオブジェクト内の「生徒A②」は別々のオブジェクト変数のはず。
なのに「生徒A.Name = "Kou"」でCollection内の「生徒A②」のNameが変更されているのがおかしい。
「set 生徒A = Nothing」で生徒Aが破棄されても、それぞれ別のオブジェクト変数として存在してるので、Collection内の生徒A②が残っていても違和感はない。
参照渡し的イメージ
参照渡し的イメージでオブジェクト変数を渡す、つまり生徒A②は生徒Aを参照しているはず。
なので「生徒A.Name ="Kou"」でCollection内の「生徒A②」のNameが変更されてもおかしくない。
しかしその後「set 生徒A = Nothing」で生徒Aが破棄されて、生徒A②が破棄されないのはおかしい。
まとめ
自分の知識の中にあった値渡し・参照渡しのいずれの考えた方を用いてもオブジェクト変数の動作を説明することが出来ず悩んでいました。
次回は色々教えて頂いた内容をまとめた(多分ある程度)正しいオブジェクト変数の動作を説明したいと思います。
今回のコードが記載された書籍