派遣事務員の迷走

派遣事務員コロ子。会社の犬。顔出しNG。常に迷走している。

コロ子勘違いしていた インスタンスの生成

こんにちは。
派犬事務員のコロ子です。

クラスのインスタンスの生成でちょっと気になる事がある。
ループをしながらDictionaryオブジェクト(Collection オブジェクトでも)のアイテムにクラス型のオブジェクトを追加する場合、ループの中でインスタンスを生成しているコードをよく見かける。

【例】
クラスモジュール:Class1

Public data As Long


'標準モジュール

Sub Test1()

Dim dic As Dictionary
Set dic = New Dictionary

Dim i As Long

For i = 1 To 3

    Dim dicitem As Class1
    Set dicitem = New Class1
    
    Dim dickey As String
    dickey = "キー" & i
    
    dicitem.data = i * 2
    
    dic.Add dickey, dicitem

Next i


Dim j As Variant
For Each j In dic.Keys

    Debug.Print dic.Item(j).data

Next j

End Sub


イミディエイトウインドウ

2
4
6

結果は正しいけど、なんか納得できない。
なんでループの中でインスタンスを生成しているのだろう?

次のコードTest2、Test3を見ると

Sub Test2()

Dim dic As Dictionary
Set dic = New Dictionary

Dim dicitem As Long

Dim i As Long

For i = 1 To 3

    Dim dickey As String
    dickey = "キー" & i

    dicitem = i * 2
    
    dic.Add dickey, dicitem

Next i


Dim j As Variant
For Each j In dic.Keys

    Debug.Print dic.Item(j)

Next j

End Sub

イミディエイトウインドウ

2
4
6

test2で、DictionaryオブジェクトのアイテムにLong型の変数を入れると値渡ししているように思える。

Sub Test3()

Dim dic As Dictionary
Set dic = New Dictionary

'先にインスタンスを生成する
Dim dicitem As Class1
Set dicitem = New Class1

Dim i As Long

For i = 1 To 3

    Dim dickey As String
    dickey = "キー" & i
    
    dicitem.data = i * 2
     
    dic.Add dickey, dicitem

Next i


Dim j As Variant
For Each j In dic.Keys

    Debug.Print dic.Item(j).data

Next j

End Sub

イミディエイトウインドウ

6
6
6


でもtest3では参照渡ししているように思える。
ということはtest1では同じ変数名で何度もインスタンスを生成しているということなの??
混乱。

仕方ないのでノンプロ研で質問してみたところ、コロ子、インスタンスの生成について分かっていなかったことが判明。

【分かった事】
①オブジェクト型の変数には参照先が格納される。
なのでTest3ではDictionaryオブジェクトのキー1~3のアイテムには同じインスタンスが格納されている。

New クラス名でいくつでもインスタンスを生成できる。
Dim 変数名 As クラス名
Set 変数名 = New クラス名

はセットで1変数1インスタンスだと思っていた。
New クラス名インスタンスを生成できる」は耳にタコができるほど聞いていたのに・・・。
Dim 変数名 As クラス名 でクラス型の変数を宣言
Set 変数名 = New クラス名 で生成したインスタンスを変数に格納するという意味だった!

Dim A As Class1
Set A = New Class1
Set A = New Class1

と書いたら、2行目のAと3行目のAには別のインスタンスが格納されている。

変数宣言はループ中に書いたら1度しか処理されないのと同じで、
Set 変数名 = New クラス名も1度しか処理されないのかと思っていた!

ちなみに次のように書くとクラス型変数の宣言が不要。

'--省略--
Dim dicKey As String
For i = 1 To 3
    dicKey = "キー" & i
    dic.Add dicKey, New Class1
    dic.Item(dicKey).data = i * 2
Next i
'--省略--

そーゆーことなのね!!


ということは、前回の記事で書いたコードはクラス型のオブジェクトを配列にしたりして、普通に分かっている人からしたら意味不明なんだろうな・・・。

クラスモジュール、シートモジュールは前回のまま
標準モジュールのみ書き直し。

Public Sub Aggregate()

Dim list As ListObject
Set list = Sheet1.ListObjects(1)

Dim ran As ListRow

Dim dic As Dictionary
Set dic = New Dictionary

Dim i As Long

'データ読み込み
For Each ran In list.ListRows

    Dim dickye As String
    dickye = Sheet1.Key(ran)
    
    If dic.Exists(dickye) Then
        'すでにkeyが存在している時は、
        Call dic.Item(dickye).Add(ran)
        
    Else
    
        'keyが存在しない(初めて)時は、
        'Dicにクラス(RawData)型のオブジェクトを格納
        
        dic.Add dickye, New RawData
        Call dic.Item(dickye).Init(ran)
        
        i = i + 1
        
    End If

Next ran

Call Sheet2.Del


'データ書き出し
i = 2
Dim j As Variant
For Each j In dic.Keys

    Sheet2.Cells(i, cnNo).Value = dic.Item(j).No
    Sheet2.Cells(i, cnLot).Value = dic.Item(j).Lot
    Sheet2.Cells(i, cnData1).Value = dic.Item(j).data1
    Sheet2.Cells(i, cnData2).Value = dic.Item(j).data2
    Sheet2.Cells(i, cnData3).Value = dic.Item(j).Data3
    
    i = i + 1
    
Next j

Call Sheet2.MakeStyle
Sheet2.Activate

End Sub

インスタンスの生成、完全に理解した!(←こーゆーのが一番怪しい)