Как должен вести себя объект, переданный в качестве аргумента ByVal - PullRequest
1 голос
/ 28 марта 2019

Я изучаю / изучаю поведение ByVal и ByRef, когда дело дошло до работы с объектом вызова.Поэтому я создал этот класс 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

И создал стандартный модуль, пытаясь увидеть, как будет изменяться значение объекта, если оно передается как ByVal или ByRef в подпрограмме s.Вот код в стандартном модуле

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

Я просто ожидал, что во второй части вызова кода Debug.Print passedPerson.fullName даст мне неизменное значение "Макс Вебер".Но вместо этого он дает новое значение "Карл Маркс".Даже если я изменю код процедуры passByVal на:

Private Sub passByVal(ByVal person As PersonModel)
    Dim newPerson As PersonModel
    Set newPerson = New PersonModel
    Set newPerson = person

    Set passedPerson = newPerson
End Sub

Вторая часть кода вызова Debug.Print passedPerson.fullName все еще выдает «Карла Маркса».Независимо от изменения ByVal на ByRef, он все равно дает тот же результат.

У меня два вопроса: 1. Так ли это на самом деле должно работать?2. Что я делаю неправильно, если моей целью является сохранение значения переменной passedPerson в "Max Weber"?

1 Ответ

3 голосов
/ 28 марта 2019

Переменная объекта не является объектом: это программная конструкция, которую мы используем для сохранения ссылки на единицу - фактический объект живет не в нашем коде, а в контексте времени выполнения VBA.

Dim objRef1 As Object
Set objRef1 = New Collection
Debug.Print ObjPtr(objRef1)

Dim objRef2 As Object
Set objRef2 = objRef1
Debug.Print ObjPtr(objRef2)

Это должно вывести один и тот же адрес дважды: обе переменные указывают на один и тот же объект: изменение свойств этого объекта одним ...

objRef1.Add 42

... повлияет на тот же объект, на который указывает другой:

Debug.Print objRef2.Count ' prints 1 even though .Add was called against objRef1

Передача объекта ByRef (это неявное значение по умолчанию) означает, что вы передаете ссылку указателю объекта, поэтому его можно упростить до , передав сам указатель : параметр ByRef теперь является локальной переменной в своей локальной области видимости, указывая на объект, на который также ссылается вызывающий объект.

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

Поскольку передается ссылка , установка ее на Nothing в любой процедуре приведет к тому, что счетчик ссылок этого объекта станет равным 0, и объект будет уничтожен. Здесь объект уничтожен и затем доступен, что вызывает ошибку 91.

Передача объекта ByVal означает, что вы передаете копию ссылки на указатель объекта - это отдельная ссылка на тот же объект :

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

Здесь локальная копия устанавливается на Nothing, но поскольку вызывающий код также имеет ссылку на этот объект, сам объект не уничтожается - поэтому элемент 42 был добавлен в коллекцию и Debug.Print выводит 1.

И это именно то, что происходит с PersonModel: его передача ByVal дает вам локальную копию указателя объекта, указывающего на тот же объект, что и вызывающий код - ByVal не выполняет глубокое клонирование целые объекты, он просто делает новую ссылку на тот же вовлеченный объект. Следовательно, изменение свойств этого объекта влияет на один и тот же объект независимо от того, передан ли указатель на этот объект по значению или по ссылке.

...