Passing Variables By Reference And By Value
This page describes the difference between passing parameters ByRef and ByVal.
Many, if not most, procedures accept input parameters that are used to calculate the result of a function or
otherwise direct the action of a procedure. These parameters may be passed By Reference (ByRef) or By Value
(ByVal).
TERMINOLOGY NOTE: Strictly speaking, the term argument refers to passed values from the perspective of the passing-from
procedure. For example, the calling procedure CallingSub passes arguments
Arg1 and Arg2 to the procedure
CalledSub. The term parameter refers to values passed into a called
procedure, from the perspective of the called procedure. For example, the procedure CalledSub
accepts parameters X and Y from
CallingSub. However, these terms are used interchangably in most documentation and will
be used interchangably on this site. The meaning will be clear from the context provided by the narrative text.
Passing ByRef or ByVal indicates whether the actual value of an argument is
passed to the CalledProcedure by the CallingProcedure, or whether a reference
(called a pointer in some other languages) is passed to to the CalledProcedure. If an
argument is passed ByRef, the memory address of the argument is passed to the
CalledProcedure and any modification to that parameter by the CalledProcedure
is made to the value in the CallingProcedure. If an argument is passed ByVal,
the actual value, not a reference to the variable, is passed to the CalledProcedure.
A simple example will illustrate this clearly:
Sub CallingProcedure()
Dim A As Long
Dim B As Long
A = 123
B = 456
Debug.Print "BEFORE CALL = A: " & CStr(A), "B: " & CStr(B)
CalledProcedure X:=A, Y:=B
Debug.Print "AFTER CALL = A: " & CStr(A), "B: " & CStr(B)
End Sub
Sub CalledProcedure(ByRef X As Long, ByVal Y As Long)
X = 321
Y = 654
End Sub
In the
CallingProcedure the variables
A and
B are
assigned the values 123 and 456, respectively. These values are confirmed with the first
Debug.Print
statement. Then, the
CalledProcedure is called passing arguments
A and
B. Within
CalledProcedure the parameters
X and
Y are assigned the values 321 and 654 respectively, and control is returned back to the procedure
CallingProcedure. Since the parameter
X was declared with
ByRef, a reference or pointer to
A was passed to
CalledProcedure
and any modification to the
X parameter in
CalledProcedure affects the variable
A in
CallingProcedure. The parameter
Y was declared
with
ByVal, so only the actual value of
B was passed to
CalledProcedure. Changes made to the parameter
Y are not made to the variable
B. This is illustrated by the second
Debug.Print statement. This shows that
A was modified by
CalledProcedure but that
B was not changed.
Objects are always passed by reference. The ByRef and ByVal
modifers indicate how the reference is passed to the called procedure. When you pass an object type variable to a procedure, the
reference or address to the object is passed -- you never really pass the object itself. When you pass an object
ByRef, the reference is passed by reference and the called procedure can change the object to which
that reference refers to. When an object is passed ByVal an copy of the reference (address) of the
object is passed.
As is so often the case, an example will serve well to illustrate this:
Sub CallingProcedure()
Dim Range1 As Range
Dim Range2 As Range
Set Range1 = Range("A1")
Set Range2 = Range("A2")
Range1.Value = 123
Range2.Value = 456
'''''''''''''''
' Debug Group 1
'''''''''''''''
Debug.Print "BEFORE CALL::Range1: " & Range1.Address(False, False) & " = " & Range1.Value
Debug.Print "BEFORE CALL::Range2: " & Range2.Address(False, False) & " = " & Range2.Value
CalledProcedure R1:=Range1, R2:=Range2
'''''''''''''''
' Debug Group 2
'''''''''''''''
Debug.Print "AFTER CALL::Range1: " & Range1.Address(False, False) & " = " & Range1.Value
Debug.Print "AFTER CALL::Range2: " & Range2.Address(False, False) & " = " & Range2.Value
End Sub
Sub CalledProcedure(ByRef R1 As Range, ByVal R2 As Range)
R1.Value = 321
R2.Value = 654
Set R1 = Range("A3")
Set R2 = Range("A4")
End Sub
In the CallingProcedure, the variable
Range1 is set to
Range("A1") and
the variable
Range2 is set to
Range("A2"). Then
CalledProcedure
is called, passing
ByRef R1 and
ByVal R2. The
CalledProcedure sets the values of these ranges to new values and then changes the ranges to which
R1 and
R2 refer. Since
R1 was passed
ByRef, the
CalledProcedure can change the cell to which
Range1 in
CallingProcedure refers to. As is confirmed by the second group
of
Debug.Print statements, the variable
Range1 now refers to (points to)
Range("A3"), not
Range("A1"). However, since
R2 was
passed
ByVal, the
CalledProcedure cannot change the range to which
Range2 refers to in
CallingProcedure.
The rules for passing your own custom classes are the same for passing object type variables.
Throughout this article, we have discussed that you cannot change the value of a variable in the calling procedure if a
parameter referring to that variable was passed ByVal. It is worth noting, for clarity, though, that you
can certainly change the value of a parameter variable regardless of whether is was passed by ByRef or
ByVal. These declarations refer to only if the variable in the calling procedure can be changed.
You can always change the value of a parameter in the called procedure. For example,
Sub CallingProcedure()
Dim A As Long
Dim B As Long
A = 1
B = 2
CalledProcedure X:=A, Y:=A
End Sub
Sub CalledProcedure(ByVal X As Long, ByVal Y)
Debug.Print "PRE:: X = " & CStr(X), "Y = " & CStr(Y)
X = 12345
Y = 54321
Debug.Print "POST:: X = " & CStr(X), "Y = " & CStr(Y)
End Sub
In this code, the
CalledProcedure is free to change the values of its parameters
X and
Y, and indeed this is often quite useful. The
ByRef and
ByVal declaration affect
only whether changes
to parameters affect the variables in the
CallingProcedure. This same rule applies to object
type parameters:
Sub CallingProcedure()
Dim Range1 As Range
Dim Range2 As Range
Set Range1 = Range("A1")
Set Range2 = Range("A2")
CalledProcedure R1:=Range1, R2:=Range2
End Sub
Sub CalledProcedure(R1 As Range, R2 As Range)
Debug.Print "PRE:: R1: " & R1.Address(False, False), " R2: " & R2.Address(False, False)
Set R1 = Range("Z1")
Set r2 = Range("Z2")
Debug.Print "POST:: R1: " & R1.Address(False, False), " R2: " & R2.Address(False, False)
End Sub
This code clearly illustrates that the object parameters
R1 and
R2 can
be changed within the scope of the
CalledProcedure procedure.
ByRef and
ByVal determine only if the changes to the parameters make their way back to the calling procedure.
Even if a called procedure has declared its parameters as ByRef, you can force those to be
ByVal by enclosing each argument within parentheses. First, examine the following code. It should look
quite familiar as we have used it before:
Sub CallingProcedure()
Dim A As Long
Dim B As Long
A = 123
B = 456
Debug.Print "PRE:: A: " & CStr(A) & " B: " & CStr(B)
CalledProcedure X:=A, Y:=B
Debug.Print "POST:: A: " & CStr(A) & " B: " & CStr(B)
End Sub
Sub CalledProcedure(ByRef X As Long, ByRef Y As Long)
X = 321
Y = 654
End Sub
The
CalledProcedure changes the values of its parameters to 321 and 654 respectively. However, imagine
the situation in which you do want to call on the functionality of
CalledProcedure but you don't want
that procedure to modify the input parameters. The difficult way would be to save the original copies of
A
and
B and restore the values after
CalledProcedure returns, or to even rewrite
the
CalledProcedures procedure altogether. However, VB/VBA gives you a much simpler method: just enclose the
arguments individually in parentheses. For example,
Sub CallingProcedure()
Dim A As Long
Dim B As Long
A = 123
B = 456
Debug.Print "PRE:: A: " & CStr(A) & " B: " & CStr(B)
CalledProcedure (A), (B)
Debug.Print "POST:: A: " & CStr(A) & " B: " & CStr(B)
End Sub
Sub CalledProcedure(ByRef X As Long, ByRef Y As Long)
X = 321
Y = 654
End Sub
Here, the second
Debug.Print statement illustrates that the values of
A and
B have not had their values changed. However, there are a few caveats to using this approach:
- You cannot use named arguments in the call to the CalledFunction.
- You (generally) cannot pass Object type parameters in this manner.
- In the circumstances in which you can use object type variable (when the object has a default property and the parameter
as declared as Variants), you may very well get incorrect results or a run time error because the
CalledProcedure is designed to work with object not simple type properties of objects.
By default, all parameters are passed by reference, so it is not necessary to include the
ByRef
declaration. However, I have over my 20 years as a professional programmer found that it is good practice to include the
ByRef declaration if you are going to change the value of the parameter. It makes no difference to how
the code runs -- in makes the code neither faster nor slower -- but it serves as documentation that the variable in the calling
procedure is going to be modified by the called procedure. It helps make the code self-documenting. You may or may not agree. Use it
or don't -- whatever style you prefer. It is worth noting that in VBNET, the default method of passing variables
is
ByVal, since everything in VBNET is a object. This may be of importance if you
are porting some VBA code to a VBNET-based document, component, or add-in.
Arrays are always passed by reference. You will receive a compiler error if you attempt to pass an array by value. See
Passing And Returning Arrays With Functions for details about passing and returning
array for VBA procedures.
This page last updated: 10-January-2013