How object passed as an argument ByVal should behave

主宰稳场 提交于 2021-01-28 14:19:37

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!