问题
I am studying/learning the bahavior of ByVal and ByRef when it comed to working with a call object. So I created this class PersonModel
Private Type TPerson
firstName As String
lastName As String
End Type
Private this As TPerson
Public Property Get firstName() As String
firstName = this.firstName
End Property
Public Property Let firstName(ByVal strNewValue As String)
this.firstName = strNewValue
End Property
Public Property Get lastName() As String
lastName = this.lastName
End Property
Public Property Let lastName(ByVal strNewValue As String)
this.lastName = strNewValue
End Property
Public Property Get fullName() As String
fullName = this.firstName & " " & this.lastName
End Property
And I made a standard module trying to see how the value of an object be affected if it's passed as ByVal or ByRef in s subroutine. Here's the code in standard module
Private passedPerson As PersonModel
Public Sub StructureType()
Dim Object1 As PersonModel
Dim Object2 As PersonModel
Set Object1 = New PersonModel
With Object1
.firstName = "Max"
.lastName = "Weber"
Debug.Print .fullName 'gives Max Weber
End With
Set Object2 = Object1 'Object2 references Object1
Debug.Print Object2.fullName 'gives Max Weber
passByVal Object1
' First Call
Debug.Print passedPerson.fullName 'gives Max Weber
With Object2
.firstName = "Karl"
.lastName = "Marx"
Debug.Print .fullName 'gives Karl Marx
End With
'Second Call
Debug.Print passedPerson.fullName 'gives Karl Marx
End Sub
Private Sub passByVal(ByVal person As PersonModel)
Set passedPerson = New PersonModel
Set passedPerson = person
End Sub
I was just expecting that in the second call part of the code Debug.Print passedPerson.fullName
will give me an unchanged value of "Max Weber". But instead, it's giving the new value "Karl Marx". Even if I change the code of the procedure passByVal to:
Private Sub passByVal(ByVal person As PersonModel)
Dim newPerson As PersonModel
Set newPerson = New PersonModel
Set newPerson = person
Set passedPerson = newPerson
End Sub
Second call part of the code Debug.Print passedPerson.fullName
is still giving "Karl Marx". Regardless of changing ByVal
to ByRef
, it's still giving the same result.
I have two questions:
1. Is this how it should really work?
2. What am I doing wrong if my aim is to keep the value of the variable passedPerson
to "Max Weber"?
回答1:
An object variable isn't an object: it's a programming construct that we use to keep a reference to one - the actual object lives not in our code, but in the VBA runtime context.
Dim objRef1 As Object
Set objRef1 = New Collection
Debug.Print ObjPtr(objRef1)
Dim objRef2 As Object
Set objRef2 = objRef1
Debug.Print ObjPtr(objRef2)
This should output the same address, twice: both variables are pointing to the same object: changing the properties of that object with one...
objRef1.Add 42
...will affect the very same object that the other also points to:
Debug.Print objRef2.Count ' prints 1 even though .Add was called against objRef1
Passing an object ByRef
(it's the implicit default) means you're passing a reference to the object pointer, so it can be simplified to passing the pointer itself: the ByRef
parameter is now a local variable in its own local scope, pointing to an object to which the caller also has a reference.
Public Sub CallingCode()
Dim objRef1 As Object
Set objRef1 = New Collection
PassByReference objRef1
Debug.Print objRef1.Count ' error 91, the object reference is gone!
End Sub
Private Sub PassByReference(ByRef thing As Object)
thing.Add 42
Set thing = Nothing
End Sub
Because the reference is being passed, setting it to Nothing
in either procedure will bring that object's reference count to 0, and the object gets destroyed. Here the object is destroyed and then accessed, which raises error 91.
Passing an object ByVal
means you're passing a copy of the reference to the object pointer - it's a distinct reference to the very same object:
Public Sub CallingCode()
Dim objRef1 As Object
Set objRef1 = New Collection
PassByValue objRef1
Debug.Print objRef1.Count ' 1
End Sub
Private Sub PassByValue(ByVal thing As Object)
thing.Add 42
Set thing = Nothing
End Sub
Here the local copy is being set to Nothing
, but since the calling code also has a reference to that object, the object itself isn't getting destroyed - so the element 42
was added to the collection, and Debug.Print
outputs 1
.
And that's exactly what's going on with your PersonModel
: passing it ByVal
gives you a local copy of the object pointer, pointing to the exact same object as the calling code - ByVal
doesn't deep-clone entire objects, it simply makes a new reference to the same involved object. Hence, modifying that object's properties affects the exact same object regardless of whether the pointer to that object is passed by value or by reference.
来源:https://stackoverflow.com/questions/55404457/how-object-passed-as-an-argument-byval-should-behave