Вступление
Модификаторы 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
или 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
не влияет на собственную копию вызывающего абонента.
In this Article
- Creating a Function without Arguments
- Calling a Function from a Sub Procedure
- Creating Functions
- Single Argument
- Multiple Arguments
- Optional Arguments
- Default Argument Value
- ByVal and ByRef
- Exit Function
- Using a Function from within an Excel Sheet
This tutorial will teach you to create and use functions with and without parameters in VBA
VBA contains a large amount of built-in functions for you to use, but you are also able to write your own. When you write code in VBA, you can write it in a Sub Procedure, or a Function Procedure. A Function Procedure is able to return a value to your code. This is extremely useful if you want VBA to perform a task to return a result. VBA functions can also be called from inside Excel, just like Excel’s built-in Excel functions.
Creating a Function without Arguments
To create a function you need to define the function by giving the function a name. The function can then be defined as a data type indicating the type of data you want the function to return.
You may want to create a function that returns a static value each time it is called – a bit like a constant.
Function GetValue() As Integer
GetValue = 50
End Function
If you were to run the function, the function would always return the value of 50.
You can also create functions that refer to objects in VBA but you need to use the Set Keyword to return the value from the function.
Function GetRange() as Range
Set GetRange = Range("A1:G4")
End Function
If you were to use the above function in your VBA code, the function would always return the range of cells A1 to G4 in whichever sheet you are working in.
Calling a Function from a Sub Procedure
Once you create a function, you can call it from anywhere else in your code by using a Sub Procedure to call the function.
The value of 50 would always be returned.
You can also call the GetRange function from a Sub Procedure.
In the above example, the GetRange Function is called by the Sub Procedure to bold the cells in the range object.
Creating Functions
Single Argument
You can also assign a parameter or parameters to your function. These parameters can be referred to as Arguments.
Function ConvertKilosToPounds (dblKilo as Double) as Double
ConvertKiloToPounds = dblKilo*2.2
End Function
We can then call the above function from a Sub Procedure in order to work out how many pounds a specific amount of kilos are.
A function can be a called from multiple procedures within your VBA code if required. This is very useful in that it stops you from having to write the same code over and over again. It also enables you to divide long procedures into small manageable functions.
In the above example, we have 2 procedures – each of them are using the Function to calculate the pound value of the kilos passed to them in the dblKilo Argument of the function.
Multiple Arguments
You can create a Function with multiple arguments and pass the values to the Function by way of a Sub Procedure.
Function CalculateDayDiff(Date1 as Date, Date2 as Date) as Double
CalculateDayDiff = Date2-Date1
End Function
We can then call the function to calculate the amount of days between 2 dates.
Optional Arguments
You can also pass Optional arguments to a Function. In other words, sometimes you may need the argument, and sometimes you may not – depending on what code you are using the Function with .
Function CalculateDayDiff(Date1 as Date, Optional Date2 as Date) as Double
'check for second date and if not there, make Date2 equal to today's date.
If Date2=0 then Date2 = Date
'calculate difference
CalculateDayDiff = Date2-Date1
End Function
VBA Coding Made Easy
Stop searching for VBA code online. Learn more about AutoMacro — A VBA Code Builder that allows beginners to code procedures from scratch with minimal coding knowledge and with many time-saving features for all users!
Learn More
Default Argument Value
You can also set the default value of the Optional arguments when you are creating the function so that if the user omits the argument, the value that you have put as default will be used instead.
Function CalculateDayDiff(Date1 as Date, Optional Date2 as Date="06/02/2020") as Double
'calculate difference
CalculateDayDiff = Date2-Date1
End Function
ByVal and ByRef
When you pass values to a function, you can use the ByVal or ByRef keywords. If you omit either of these, the ByRef is used as the default.
ByVal means that you are passing a copy of the variable to the function, whereas ByRef means you are referring to the original value of the variable. When you pass a copy of the variable (ByVal), the original value of the variable is NOT changed, but when you reference the variable, the original value of the variable is changed by the function.
Function GetValue(ByRef intA As Integer) As Integer
intA = intA * 4
GetValue = intA
End Function
In the function above, the ByRef could be omitted and the function would work the same way.
Function GetValue(intA As Integer) As Integer
intA = intA * 4
GetValue = intA
End Function
To call this function, we can run a sub-procedure.
Sub TestValues()
Dim intVal As Integer
'populate the variable with the value 10
intVal = 10
'run the GetValue function, and show the value in the immediate window
Debug.Print GetValue(intVal)
'show the value of the intVal variable in the immediate window
Debug.Print intVal
End Sub
Note that the debug windows show the value 40 both times. When you pass the variable IntVal to the function – the value of 10 is passed to the function, and multiplied by 4. Using the ByRef keyword (or omitting it altogether), will AMEND the value of the IntVal variable. This is shown when you show first the result of the function in the immediate window (40), and then the value of the IntVal variable in the debug window (also 40).
If we do NOT want to change the value of the original variable, we have to use ByVal in the function.
Function GetValue(ByVal intA As Integer) As Integer
intA = intA * 4
GetValue = intA
End Function
Now if we call the function from a sub-procedure, the value of the variable IntVal will remain at 10.
Exit Function
If you create a function that tests for a certain condition, and once the condition is found to be true, you want return the value from the function, you may need to add an Exit Function statement in your Function in order to exit the function before you have run through all the code in that function.
Function FindNumber(strSearch As String) As Integer
Dim i As Integer
'loop through each letter in the string
For i = 1 To Len(strSearch)
'if the letter is numeric, return the value to the function
If IsNumeric(Mid(strSearch, i, 1)) Then
FindNumber= Mid(strSearch, i, 1)
'then exit the function
Exit Function
End If
Next
FindNumber= 0
End Function
The function above will loop through the string that is provided until it finds a number, and then return that number from the string. It will only find the first number in the string as it will then Exit the function.
The function above can be called by a Sub routine such as the one below.
Sub CheckForNumber()
Dim NumIs as Integer
'pass a text string to the find number function
NumIs = FindNumber("Upper Floor, 8 Oak Lane, Texas")
'show the result in the immediate window
Debug.Print NumIs
End Sub
VBA Programming | Code Generator does work for you!
Using a Function from within an Excel Sheet
In addition to calling a function from your VBA code using a sub procedure, you can also call the function from within your Excel sheet. The functions that you have created should by default appear in your function list in the User Defined section of the function list.
Click on the fx to show the Insert Function dialog box.
Select User Defined from the Category List
Select the function you require from the available User Defined Functions (UDF’s).
Alternatively, when you start writing your function in Excel, the function should appear in the drop down list of functions.
If you do not want the function to be available inside an Excel sheet, you need to put the Private word in front of the word Function when you create the function in your VBA code.
Private Function CalculateDayDiff(Date1 as Date, Date2 as Date) as Double
CalculateDayDiff = Date2-Date1
End Function
It will now not appear in the drop down list showing the Excel functions available.
Interestingly enough, however, you can still use the function – it just will not appear in the list when looking for it!
If you have declared the second argument as Optional, you can omit it within the Excel sheet as well as within the VBA code.
You can also use the a function that you have created without arguments in your Excel sheet.
I’ve tried to attempt something that was answered by JaredPar ByRef vs ByVal Clarification
ByVal
in VB.NET means that a copy of the provided value will be sent
to the function. For value types (Integer
,Single
, etc.) this will
provide a shallow copy of the value. With larger types this can be
inefficient. For reference types though (String
, class instances) a
copy of the reference is passed. Because a copy is passed in mutations
to the parameter via=
it won’t be visible to the calling function.
ByRef
in VB.NET means that a reference to the original value will be
sent to the function (1). It’s almost like the original value is being
directly used within the function. Operations like=
will affect the
original value and be immediately visible in the calling function.
And I’ve tried to test it with the following code and I can’t seem to get it to work use the ByRef
to change the value of the cell to 0
if it’s 1
Here’s my below code that I’m testing with, what am I doing wrong? The value of Range("A1")
is still 1
Sub test1()
Dim trythis As Boolean
trythis = False
If (Sheets("TESTING").Range("A1").value = 1) Then
testingRoutine (trythis)
If (trythis) Then
Debug.Print "Value changed to 0"
Sheets("TESTING").Range("A1").value = 0
End If
End If
End Sub
Private Function testingRoutine(ByRef trythis As Boolean)
Debug.Print "Ran Function"
trythis = True
End Function
asked Oct 26, 2017 at 17:00
6
VB subroutines don’t require braces around the argument list. However, if you pass a single argument and you enclose that in braces, you are passing an expression . Expressions cannot be passed by reference. They are evaluated and their result is passed. Therefore you must remove the braces in the call testingRoutine (trythis)
and write testingRoutine trythis
Note: if you call a function without using its return value, it must be written as a procedure call (without braces around the argument list). As an example:
myVal = myFunction (trythis) ' trythis will be passed by reference
myFunction (trythis) ' trythis will be seen as an expression
myFunction trythis ' trythis will be passed by reference
myVal = mySub (trythis) ' invalid: mySub is not a function
mySub (trythis) ' trythis will be seen as an expression
mySub trythis ' trythis will be passed by reference
Of course, the problem will be clear immediately when a function or sub has more than one parameter because a comma cannot appear in an expression.
answered Oct 26, 2017 at 17:12
Paul OgilviePaul Ogilvie
25k4 gold badges21 silver badges41 bronze badges
2
Change this:
testingRoutine (trythis)
to this:
testingRoutine trythis
then try it.
Also, look what happens if I change it to this, where the function is being used as a function (returning something)
Sub test1()
Dim trythis As Boolean
Dim testit As Boolean
trythis = False
If (Sheets("Sheet1").Range("A1").Value = 1) Then
testit = testingRoutine(trythis)
If (trythis) Then
Debug.Print "Value changed to 0"
Sheets("TESTING").Range("A1").Value = 0
End If
End If
End Sub
Private Function testingRoutine(ByRef trythis As Boolean) As Boolean
Debug.Print "Ran Function"
trythis = True
End Function
answered Oct 26, 2017 at 17:08
braXbraX
11.5k5 gold badges20 silver badges33 bronze badges
4
I would do it this way.
Sub test1()
Dim trythis As Boolean
trythis = False
If (Sheets("TESTING").Range("A1").value = 1) Then
tr = testingRoutine(trythis)
If tr Then
Debug.Print "Value changed to 0"
Sheets("TESTING").Range("A1").value = 0
End If
End If
End Sub
Private Function testingRoutine(ByRef trythis As Boolean)
Debug.Print "Ran Function"
testingRoutine = True
End Function
Because trythis
is not a global variable, changing it in the function will do nothing in the sub. It will just define trythis
as True in the function and stay in that scope. To get trythis
to be affected by the function and read by the sub you have to assign it to a variable within the sub.
answered Oct 26, 2017 at 17:13
n8.n8.
1,7313 gold badges16 silver badges38 bronze badges
i can’t remember where i read this, but i know that if you add the bracets () to arguments when calling a sub/function, it is a specific (not an error) way microsoft implented to workaround byref calls (to byval).
example : lets say you call a sub who has ONLY byref, and 2 arguments:
call Sub1 (a), (b)
is a proper way to force Byval
arguments, even if they were initially coded as Byval
answered Oct 26, 2017 at 21:30
1