VBA
Передача аргументов ByRef или ByVal
Поиск…
Вступление
Модификаторы ByRef
и ByVal
являются частью сигнатуры процедуры и указывают, как передается аргумент процедуре. В VBA параметр передается ByRef
если не указано иное (т.е. ByRef
неявно, если отсутствует).
Примечание. Во многих других языках программирования (включая VB.NET) параметры неявно передаются по значению, если не указан модификатор: подумайте о том, чтобы указать модификаторы ByRef
явно, чтобы избежать возможной путаницы.
замечания
Передача массивов
Массивы должны передаваться по ссылке. Этот код компилируется, но вызывает ошибку времени выполнения 424 «Object Required»:
Public Sub Test()
DoSomething Array(1, 2, 3)
End Sub
Private Sub DoSomething(ByVal foo As Variant)
foo.Add 42
End Sub
Этот код не компилируется:
Private Sub DoSomething(ByVal foo() As Variant) 'ByVal is illegal for arrays
foo.Add 42
End Sub
Передача простых переменных ByRef And ByVal
Передача ByRef
или ByVal
указывает, ByVal
ли фактическое значение аргумента CalledProcedure
CallingProcedure
, или же ссылка (называемая указателем на некоторых других языках) передается CalledProcedure
.
Если аргумент передан ByRef
, адрес памяти аргумента передается в CalledProcedure
и любая модификация этого параметра CalledProcedure
выполняется в значение CallingProcedure
.
Если аргумент передается ByVal
, фактическое значение, а не ссылка на переменную, передается в CalledProcedure
.
Простой пример будет ясно иллюстрировать это:
Sub CalledProcedure(ByRef X As Long, ByVal Y As Long)
X = 321
Y = 654
End Sub
Sub CallingProcedure()
Dim A As Long
Dim B As Long
A = 123
B = 456
Debug.Print "BEFORE CALL => A: " & CStr(A), "B: " & CStr(B)
''Result : BEFORE CALL => A: 123 B: 456
CalledProcedure X:=A, Y:=B
Debug.Print "AFTER CALL = A: " & CStr(A), "B: " & CStr(B)
''Result : AFTER CALL => A: 321 B: 456
End Sub
Другой пример:
Sub Main()
Dim IntVarByVal As Integer
Dim IntVarByRef As Integer
IntVarByVal = 5
IntVarByRef = 10
SubChangeArguments IntVarByVal, IntVarByRef '5 goes in as a "copy". 10 goes in as a reference
Debug.Print "IntVarByVal: " & IntVarByVal 'prints 5 (no change made by SubChangeArguments)
Debug.Print "IntVarByRef: " & IntVarByRef 'prints 99 (the variable was changed in SubChangeArguments)
End Sub
Sub SubChangeArguments(ByVal ParameterByVal As Integer, ByRef ParameterByRef As Integer)
ParameterByVal = ParameterByVal + 2 ' 5 + 2 = 7 (changed only inside this Sub)
ParameterByRef = ParameterByRef + 89 ' 10 + 89 = 99 (changes the IntVarByRef itself - in the Main Sub)
End Sub
ByRef
Модификатор по умолчанию
Если для параметра не указан модификатор, этот параметр неявно передается по ссылке.
Public Sub DoSomething1(foo As Long)
End Sub
Public Sub DoSomething2(ByRef foo As Long)
End Sub
Параметр foo
передается ByRef
как в DoSomething1
и DoSomething2
.
Осторожно! Если вы приходите в VBA с опытом работы на других языках, это, скорее всего, совершенно противоположное поведение с тем, к которому вы привыкли. Во многих других языках программирования (включая VB.NET) неявный / по умолчанию модификатор передает параметры по значению.
Передача по ссылке
Когда значение передается
ByRef
, процедура получает ссылку на значение.Public Sub Test() Dim foo As Long foo = 42 DoSomething foo Debug.Print foo End Sub Private Sub DoSomething(ByRef foo As Long) foo = foo * 2 End Sub
Вызов вышеуказанных выходов
Test
процедуры 84.DoSomething
присваиваетсяfoo
и получает ссылку на значение, и поэтому работает с тем же адресом памяти, что и вызывающий.Когда ссылка передается
ByRef
, процедура получает ссылку на указатель.Public Sub Test() Dim foo As Collection Set foo = New Collection DoSomething foo Debug.Print foo.Count End Sub Private Sub DoSomething(ByRef foo As Collection) foo.Add 42 Set foo = Nothing End Sub
Вышеприведенный код повышает ошибку 91 во время выполнения , поскольку вызывающий абонент вызывает член
Count
объекта, который больше не существует, посколькуDoSomething
получил ссылку на указатель объекта и назначил егоNothing
перед возвратом.
Принуждение ByVal на сайте вызова
Используя круглые скобки на сайте вызова, вы можете переопределить ByRef
и принудительно передать аргумент ByVal
:
Public Sub Test()
Dim foo As Long
foo = 42
DoSomething (foo)
Debug.Print foo
End Sub
Private Sub DoSomething(ByRef foo As Long)
foo = foo * 2
End Sub
Вышеуказанные выходы кода 42, независимо от того, указан ли ByRef
неявно или явно.
Осторожно! Из-за этого, используя посторонние круглые скобки в процедурных вызовах, можно легко ввести ошибки. Обратите внимание на пробелы между именем процедуры и списком аргументов:
bar = DoSomething(foo) 'function call, no whitespace; parens are part of args list DoSomething (foo) 'procedure call, notice whitespace; parens are NOT part of args list DoSomething foo 'procedure call does not force the foo parameter to be ByVal
ByVal
Передача по значению
Когда значение передается
ByVal
, процедура получает копию значения.Public Sub Test() Dim foo As Long foo = 42 DoSomething foo Debug.Print foo End Sub Private Sub DoSomething(ByVal foo As Long) foo = foo * 2 End Sub
Вызов вышеуказанных выходов
Test
процедуры 42.DoSomething
присваиваетсяfoo
и получает копию значения. Копия умножается на 2, а затем отменяется, когда процедура завершается; копия вызывающего абонента никогда не изменялась.Когда ссылка передается
ByVal
, процедура получает копию указателя.Public Sub Test() Dim foo As Collection Set foo = New Collection DoSomething foo Debug.Print foo.Count End Sub Private Sub DoSomething(ByVal foo As Collection) foo.Add 42 Set foo = Nothing End Sub
Вызов вышеуказанных результатов
Test
процедуры 1.DoSomething
предоставляетсяfoo
и получает копию указателя на объектCollection
. Поскольку переменная объектаfoo
в областиTest
указывает на один и тот же объект, добавление элемента вDoSomething
добавляет элемент к одному и тому же объекту. Поскольку это копия указателя, установка ссылки наNothing
не влияет на собственную копию вызывающего абонента.