PassingByVal / ByRef

dugdugdug

Active Member
Joined
May 11, 2012
Messages
342
We know the default to pass simple variable args is ByRef, so you never need to write ByRef, unless you want to be specific.

What about object variables?

According to this article by Chip Pearson, he states objects are ALWAYS passed ByRef:

Passing Variable ByRef And ByVal

Does he mean the DEFAULT is ByRef or does he mean it can ONLY be passed ByRef?

In a class, if you inserted a property using Insert - Procedure - Property, you get the following:

Code:
Public Let Property (ByVal MyVar) As SomeType

But some books omit the keyword ByVal, so are they he same, ie the default is ByVal? (If you added ByRef instead, does it make a difference)?

Similarly for the Set Property, if you added ByVal, or ByRef or omitted it altogether, what's the difference?

Thanks
 
Last edited:
I'll let you get your coffee, digest it, then if you do not add anything else here, I assume the last thing you say is correct!
 
Upvote 0

Excel Facts

What do {} around a formula in the formula bar mean?
{Formula} means the formula was entered using Ctrl+Shift+Enter signifying an old-style array formula.
dugdugdug, In response to your PM.

I'm not sure what claim that you asked me to evaluate.

I do agree that it is Best Practice to always specify ByRef or ByVal rather than depending on defaults.

As far as my personal practice, I don't always do it. But when it matters to me, I always specify it even if it will be taken care of by default. (If I don't ask for it, I can't complain if I don't get it.)

As far as books, there are some whose authors write with the same precision that I write code. (i.e. sometimes BestPractices, sometimes not). Until I start writing my code using Best Practices all the time, I'm not in a position to critique their writing style.

I know that when answering here, I sometimes present code that is (IMO) Bad Practice, just to avoid blurring what I'm trying to show the questioner. I suspect that book authors also sometimes ignore Best Practices for heuristic effect.
 
Last edited:
Upvote 0
dugdugdug, In response to your PM.

I'm not sure what claim that you asked me to evaluate.

I do agree that it is Best Practice to always specify ByRef or ByVal rather than depending on defaults.

As far as my personal practice, I don't always do it. But when it matters to me, I always specify it even if it will be taken care of by default. (If I don't ask for it, I can't complain if I don't get it.)

As far as books, there are some whose authors write with the same precision that I write code. (i.e. sometimes BestPractices, sometimes not). Until I start writing my code using Best Practices all the time, I'm not in a position to critique their writing style.

I know that when answering here, I sometimes present code that is (IMO) Bad Practice, just to avoid blurring what I'm trying to show the questioner. I suspect that book authors also sometimes ignore Best Practices for heuristic effect.

Sorry but I made a mistake myself. I was trying to get your opinion on another thread but mis-typed.

In any case, as you are, I would welcome your thoughts here, if you don't mind.

Esssentially, what I want to know is the following:

Are these three all the same:

Code:
Property Let MyVar(ByVal MyVa As SomeType)
Property Let MyVar(ByRef MyVa As SomeType)
Property Let MyVar(MyVa As SomeType)

likewise for these three:

Code:
Property Set MyVar(ByVal MyVa As SomeType)
Property Set MyVar(ByRef MyVa As SomeType)
Property Set MyVar(MyVa As SomeType)

RoryA was of the impression there weren't but then corrected himself, saying property procedures always pass ByVal.

However, according to Chip Pearson here:

Passing Variable ByRef And ByVal

at the bottom, he states Arrays are ALWAYS passed by reference.

So what happens if you pass an array via property?

Thanks
 
Last edited:
Upvote 0
So what happens if you pass an array via property?

You can only return an array from a Property Get - you cannot pass an array to a Property Let procedure. You have to use a Variant that contains an array.
 
Upvote 0
No they aren't
Code:
Property Let MyVar(ByVal MyVa As SomeType)
Property Let MyVar(ByRef MyVa As SomeType)
Property Let MyVar(MyVa As SomeType)

Consider a class
Code:
Dim pCollection As Collection
Dim pPointer As Long

Property Get NextObject(ByRef Overflow As Boolean) As Object
    pPointer = (pPointer Mod pCollection.Count) + 1
    Overflow = (pPointer = 1)
    Set NextObject = pCollection.Item(pPointer)
End Property


To loop through all the items in pCollection, the coder would use code like

Code:
Dim OVF as Boolean
dim aThing as Class1
Dim oneObject as Object

Do Until OVF
    Set oneObject = aThing.NextObject(OVF)
    ' do something with oneObject
Loop

The property is passing more than one piece of information back to the calling routine, the nextObject and the "did it loop?" Overflow argument.
The Overflow argument has to be declared ByRef for this to work right. otherwise the Do loop never terminates

There is a similar requirement if the property were recast

Code:
Property Get OverFlow(ByRef NextObject As Object) As Boolean
    pPointer = (pPointer Mod pCollection.Count) + 1
    Overflow = (pPointer = 1)
    Set NextObject = pCollection.Item(pPointer)
End Property

called by
Code:
Do Until aThing.Overflow(oneObject)
    ' do something with oneObject
Loop

(Look at the .Find function of a CodeModule object for a similar example of passing more than one piece of information)

If there were a matching Property Let for those properties, the ByRef/ByVal would have to match.
And, if I squinted enough, I could probably come up with a Property Let where passing data back would be useful.
 
Last edited:
Upvote 0
No they aren't
Code:
Property Let MyVar(ByVal MyVa As SomeType)
Property Let MyVar(ByRef MyVa As SomeType)
Property Let MyVar(MyVa As SomeType)

Consider a class
Code:
Dim pCollection As Collection
Dim pPointer As Long

Property Get NextObject(ByRef Overflow As Boolean) As Object
    pPointer = (pPointer Mod pCollection.Count) + 1
    Overflow = (pPointer = 1)
    Set NextObject = pCollection.Item(pPointer)
End Property


To loop through all the items in pCollection, the coder would use code like

Code:
Dim OVF as Boolean
dim aThing as Class1
Dim oneObject as Object

Do Until OVF
    Set oneObject = aThing.NextObject(OVF)
    ' do something with oneObject
Loop

The property is passing more than one piece of information back to the calling routine, the nextObject and the "did it loop?" Overflow argument.
The Overflow argument has to be declared ByRef for this to work right. otherwise the Do loop never terminates

There is a similar requirement if the property were recast

Code:
Property Get OverFlow(ByRef NextObject As Object) As Boolean
    pPointer = (pPointer Mod pCollection.Count) + 1
    Overflow = (pPointer = 1)
    Set NextObject = pCollection.Item(pPointer)
End Property

called by
Code:
Do Until aThing.Overflow(oneObject)
    ' do something with oneObject
Loop

(Look at the .Find function of a CodeModule object for a similar example of passing more than one piece of information)

If there were a matching Property Let for those properties, the ByRef/ByVal would have to match.
And, if I squinted enough, I could probably come up with a Property Let where passing data back would be useful.

RoryA,

Does that mean Mick's response contradicts your own correction then (and therefore you were correct all along)?

Sorry to dwell on this but really want it nailed once and for all.
 
Upvote 0
Here's an example where ByVal is required.
When passed one index, the Element property returns a value as if it were a one dimensional array, when passed two, it acts like its a two dimensional array.
It mimics the .Cells property where Range("A1:C10").Cells(4) refers to A2.

Code:
'in Class1
Dim pArray(1 To 10, 1 To 10) As String

Property Get Element(ByVal rIndex As Long, Optional ByVal cIndex As Long = 0) As String
    If cIndex = 0 Then
        cIndex = rIndex Mod 10
        If cIndex = 0 Then cIndex = 1
        rIndex = WorksheetFunction.Ceiling(rIndex, 10) / 10
    End If
    Element = pArray(rIndex, cIndex)
End Property

Property Let Element(ByVal rIndex As Long, Optional ByVal cIndex As Long = 0, inVal As String)
    If cIndex = 0 Then
        cIndex = rIndex Mod 10
        If cIndex = 0 Then cIndex = 1
        rIndex = WorksheetFunction.Ceiling(rIndex, 10) / 10
    End If
    pArray(rIndex, cIndex) = inVal
End Property

Private Sub Class_Initialize()
    Dim i As Long, j As Long
    For i = 1 To 10
        For j = 1 To 10
            pArray(i, j) = i & ", " & j
        Next j
    Next i
End Sub
Code:
Sub test()
    Dim myObject As New Class1
    
    MsgBox myObject.Element(2, 1) = myObject.Element(11): Rem True
    
    myObject.Element(12) = "cat"
    
    MsgBox myObject.Element(2, 2): Rem cat
End Sub
 
Upvote 0
dugdugdug - you aren't dragging this out - this is a good thread.
And I am rather embarrassed at the lack of proper declaration in the book.... I never even noticed it wasn't there. I always declare my variables, properties, etc, not depending on the defaults.. as you can see in this thread, one can forget or get confused...
 
Upvote 0
Someone has asked me to summarize this thread.

The shortest summary would be: Don't rely on defaults. Specify what you want to happen.
My favorite example of this isn't in the ByVal/ByRef area, but with Split. Specifying the delimiter in Split, arrX = Split(strX, " "), makes revision easier and saves me from having to remember Split's default delimiter.

A longer summary would include a discussion of ByRef and ByVal.

When an argument is passed to a procedure (Sub, Function or Property (Get or Let)), if it is passed ByRef, any changes that the receiving procedure are reflected in the variable that the calling procedure passed. If it is passed ByVal, those changes are NOT reflected in the calling procedure's variables.

Code:
Sub aTest()
    Dim aString As String

    aString = "A"
    ByValSub aString
    MsgBox "after byVal Sub: " & aString
    
    aString = "A"
    ByRefSub aString
    MsgBox "after byRef Sub: " & aString
End Sub

Sub ByRefSub(ByRef X As String)
    X = X & X
    MsgBox "inside value: " & X
End Sub

Sub ByValSub(ByVal X As String)
    X = X & X
    MsgBox "inside value: " & X
End Sub

This distinction also applies when the argument being passed is an object, but one has to remember that ByRef and ByVal effect variables not the objects that they represent.

In the following example bValSub and vRefSub make the same changes to the objects (worksheets) that are passed to them, but the variable's that represent those objects are handled differently.

Code:
Sub test2()
    Dim aWorksheet As Worksheet
    
    Set aWorksheet = ThisWorkbook.Sheets(1)
    Call bValSub(aWorksheet)
    MsgBox "after ByVal: " & aWorksheet.Name
    
    Set aWorksheet = ThisWorkbook.Sheets(1)
    Call bRefSub(aWorksheet)
    MsgBox "after ByRef: " & aWorksheet.Name
End Sub

Sub bValSub(ByVal mySheet As Worksheet)
    With ThisWorkbook
        Set mySheet = .Sheets((mySheet.Index Mod .Sheets.Count) + 1)
    End With
    mySheet.Range("A1").Value = mySheet.Range("A1").Value + 1
End Sub

Sub bRefSub(ByRef mySheet As Worksheet)
    With ThisWorkbook
        Set mySheet = .Sheets((mySheet.Index Mod .Sheets.Count) + 1)
    End With
    mySheet.Range("A1").Value = mySheet.Range("A1").Value + 1
End Sub

A general rule could be that when an argument is changed by a procedure, that argument should be passed ByVal, unless reading that change is what is desired.

Passing an argument ByRef is one way to get multiple results from one procedure, as in the following.

Code:
Sub testTrig()
    Dim Angle As Double, Dist As Double
    Dim xVal As Double, yVal As Double
    
    xVal = -3: yVal = 4
    
    Angle = RectToPolarAngle(xVal, yVal, Dist)
    MsgBox "Cartesian(" & xVal & ", " & yVal & ") = polar (" & WorksheetFunction.Degrees(Angle) & "°, " & Dist & ")"
End Sub

Function RectToPolarAngle(X As Double, y As Double, Optional ByRef Distance As Double) As Double
    RectToPolarAngle = Atn(y / X)
    If X < 0 Then RectToPolarAngle = (Sgn(y) * WorksheetFunction.Pi / 2) - RectToPolarAngle
    Distance = Sqr(X ^ 2 + y ^ 2)
End Function

Another example of where ByVal is important is recursive routines. The primary argument of a recursive routine should be ByVal, but "pass through" arguments (like Descending and Delimiter) should be ByVal, so that no extra memory is used creating unneeded duplicates of the same value.
Code:
Sub tester()
    MsgBox SortedString("bat,c,e,ace,cat", True)
End Sub

Function SortedString(ByVal DelimitedString As String, Optional ByVal Descending As Boolean, Optional ByVal Delimiter As String = ",") As String
    Dim Words As Variant
    Dim strLeft As String, strPivot As String, strRight As String
    Dim i As Long
    
    Words = Split(DelimitedString, Delimiter)
    
    If UBound(Words) < 1 Then
        SortedString = DelimitedString
    Else
        strPivot = Words(0)
        For i = 1 To UBound(Words)
            If Words(i) < strPivot Xor Descending Then
                strLeft = strLeft & Delimiter & Words(i)
            Else
                strRight = strRight & Delimiter & Words(i)
            End If
        Next i
        
        strLeft = Mid(strLeft, Len(Delimiter) + 1)
        strRight = Mid(strRight, Len(Delimiter) + 1)
        
        If strLeft <> vbNullString Then
            strLeft = SortedString(strLeft, Descending:=Descending, Delimiter:=Delimiter)
            strPivot = strLeft & Delimiter & strPivot
        End If
        If strRight <> vbNullString Then
            strRight = SortedString(strRight, Descending:=Descending, Delimiter:=Delimiter)
            strPivot = strPivot & Delimiter & strRight
        End If
        
        SortedString = strPivot
    End If
End Function

This thread also mentioned passing arrays to procedures. My Mac handles arrays differently than Windows machines, so I always pass them as Variants.

From what I can tell, the default for all procedures is to pass arguments ByRef. But if it matters, that should be specified. (Excel is going to apply the default anyway, there is no loss by specifying.)

I hope this summary is what was sought by the request.

I almost forgot to mention:
User Defined Classes (in class modules) can be passed either ByRef or ByVal
User defined Types cannot be passed ByVal, they may only be passed ByRef.

Code:
' works 

Type MyType
    Tag As String
End Type

Sub atest()
    Dim aVal As MyType
    aVal.Tag = "hello"
    Call typeTag(aVal)
End Sub

Sub TypeVal(ByRef aThing As MyType)
    MsgBox aThing.Tag
End Sub

Code:
' gives Compile Error

Type MyType
    Tag As String
End Type

Sub atest()
    Dim aVal As MyType
    aVal.Tag = "hello"
    Call typeTag(aVal)
End Sub

Sub TypeVal(ByVal aThing As MyType)
    MsgBox aThing.Tag
End Sub
 
Last edited by a moderator:
Upvote 0

Forum statistics

Threads
1,214,968
Messages
6,122,509
Members
449,089
Latest member
RandomExceller01

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top