ThreeWave Passing Variables By Reference And By Value

This page describes the difference between passing parameters ByRef and ByVal.
ShortFadeBar

Introduction

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.

SectionBreak

Passing Simple Variables ByRef And ByVal

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.

SectionBreak

Passing Objects ByRef And ByVal

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.

SectionBreak

Passing User Created Classes ByRef And ByVal

The rules for passing your own custom classes are the same for passing object type variables.

SectionBreak

Changing Parameters In Called Procedures

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.

SectionBreak

Converting A ByRef Parameter To ByVal

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.

SectionBreak

The Default Method Of Passing Parameters

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.

SectionBreak

Passing Arrays

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

-->