VBA: Difference in two ways of declaring a new object? (Trying to understand why my solution works)
Asked Answered
P

3

30

I was creating a new object within a loop, and adding that object to a collection; but when I read back the collection after, it was always filled entirely with the last object I had added. I've come up with two ways around this, but I simply do not understand why my initial implementation was wrong.

Original:

Dim oItem As Variant
Dim sOutput As String
Dim i As Integer

Dim oCollection As New Collection
For i = 0 To 10
    Dim oMatch As New clsMatch
    oMatch.setLineNumber i
    oCollection.Add oMatch
Next
For Each oItem In oCollection
    sOutput = sOutput & "[" & oItem.lineNumber & "]"
Next
MsgBox sOutput

This resulted in every lineNumber being 10; I was obviously not creating new objects, but instead using the same one each time through the loop, despite the declaration being inside of the loop.

So, I added Set oMatch = Nothing immediately before the Next line, and this fixed the problem, it was now 0 to 10. So if the old object was explicitly destroyed, then it was willing to create a new one? I would have thought the next iteration through the loop would cause anything declared within the loop do be destroyed due to scope?

Curious, I tried another way of declaring a new object: Dim oMatch As clsMatch: Set oMatch = New clsMatch. This, too, results in 0 to 10.

Can anyone explain to me why the first implementation was wrong?

Providence answered 19/3, 2010 at 14:40 Comment(2)
In VBA is considered bad practice to use "As New". As you discovered the hard way, it can lead to all kinds of subtle errors. It is best to declare all your variables at the top and use "= New" where appropriate. (Side note: I would say the exact opposite in VB.Net)Scuta
The smallest possible scope in VBA is procedure-level; a For...Next block (or any other block) does not create a scope.Hobbie
A
33

Fink's answer gets your main problem right, which is that your first loop is adding multiple references to the same instance of 'clsMatch' to your collection. I'll just elaborate on why your fix works.

In VBA, a line like:

Dim c As New Collection

doesn't actually create a new collection. The 'Dim' statement is always just a declaration. Think of the 'As New' form as being shorthand for this:

Dim c As Collection
'...

'(later, when you're about to use 'c')

If c Is Nothing Then
    Set c = New Collection
End If

'...

That is why destroying your reference by setting the variable that contained it to 'Nothing' was working. [NOTE: to whomever edited this to say "was not" - that changes the meaning of the answer and makes it incorrect. Please read the original question. The OP found that setting the variable to Nothing did work, and I was explaing why that was the case.] When the loop came back around to the 'oMatch.setLineNumber' line, VBA "helpfully" created a new instance of 'clsMatch' for your 'oMatch' variable to refer to, and then you got multiple different instances in your collection.

It would probably be better to do this explicitly:

Dim oMatch As clsMatch   

For i = 0 To 10                
    Set oMatch = New clsMatch                
    oMatch.setLineNumber i                
    oCollection.Add oMatch                
Next  

Note that (unlike in C/C++ or ??.NET) it doesn't matter where the 'Dim' declaration goes. It's not being "executed" multiple times inside the loop, and the scope of what it declares is procedure-wide even though it appears inside the loop.

Alisonalissa answered 19/3, 2010 at 21:1 Comment(2)
Thank you for explaining that the scope isn't like C/C++, that is what I am more used to, as far as expected behaviour (learned first on C, so I just assume all languages will act similarly).Providence
That's why you'll often see (correct, IMO) counsel like Jonathan Allen's comment to your question that 'As New' is usually best avoided. 'Dim <variable> As <class>' declares stuff. 'Set <variable> = New <class>' creates stuff. But 'Dim <variable> As New <class>' is a weird hybrid. Combine the fact that the 'Dim As New' form looks like it's creating an instance right then with the fact that VBA will let you put it in a loop and confusion is understandable.Alisonalissa
P
9

When your adding the oMatch object to the collection, its passing the variable By Memory Reference. When you are declaring oMatch again as a new clsMatch, its not destroying the first objects local memory pointer you had created. Its simply giving you the same local memory location as the first oMatch object you had created even though you have declared it as a new object. VBA uses ByRef as the default memory passing technique. The collection memory locations are then updated, both pointing to the same memory location, with the newly updated line number. Thus all collection memory pointers are going to point to the same last object you had created.

When you set oMatch = nothing, it resets the local memory pointer, and will create a new oMatch object with a new local memory pointer, and the collection's pointers will all point to their correct objects.

VBA's default memory passing is ByRef, as apposed to VB where the default is ByVal, so you might run into this caveat every now and again.

Puny answered 19/3, 2010 at 16:58 Comment(0)
C
3

There is a valid use for "as new" within class modules. Consider this:

module a:

Dim mUbelow as myClassX       ' do not use "as new" here 
set mUbelow = new myClassX    ' mUbelow instanciation also instanciates subClass 
                              ' as a referencedClass object
                              ' so you can not forget to do this
mUbelow.subClass.someThing = "good news"  ' without the "as new" below: ==> error

class myClassX:

Public subClass as new referencedClass ' automatic instanciation of subclass:

class referencedClass:

Public someThing as string
Calvities answered 2/2, 2015 at 11:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.