by reference (1 Viewer)

In my experience in Access, declaring an Object or Array as ByVal is ignored because they are always ByRef.
 
Another thing worth mentioning is the effect of parentheses on an argument. It causes them to be passed ByVal regardless of the declaration.

Many developers get caught by this when they pass a object in a sub with a single argument but use function syntax.
This will pass the value property of the control and give a type mismatch if SomeSub is expecting a Control Object.
Code:
SomeSub (Me.somecontrol)
 
In my experience in Access, declaring an Object or Array as ByVal is ignored because they are always ByRef.

The documentation says there is a nuance of a difference of declaring an object passage ByRef vs ByVal, perhaps most often encountered when the formal variable is of type Variant and the actual variable is an object.
 
I understood that when you call an argument byval, a copy of the variable is created on the stack/heap and the address that gets transferred is the address of the copy - so changing the value of the argument changes the value of the copy, and not the real variable.

However some complex structures can only be transferred byref - which may have been why @The_Doc_Man had a problem with his genealogy routine. I think there are other structures that get passed byref also.


I read this
Array argument must be ByRef | Microsoft Docs

But that's really odd. I thought that arrays declared as byval were still passed as byref, but I thought I would test it to see if I got an error, or whether it just ignored the byval

So I tried this - and I must say I was surprised that changing the argument type in doublearray from byref to byval seemed to work. No error. Very strange.

Code:
Sub tryArray()
Dim r(2, 10) As Integer

Dim x As Long
Dim s As String

     r(1, 1) = 1
     r(1, 2) = 2
     r(1, 3) = 3

     doublearray r
     showArray r, "Original after processing"

End Sub

Function doublearray(ByVal r)

Dim x As Long

    For x = 0 To 10
         r(1, x) = r(1, x) * 2
    Next
    showArray r, "Byval Function"
End Function

Sub showArray(r, description As String)
Dim s As String
Dim x As Long

      s = description & vbCrLf & vbCrLf
      For x = 0 To 10
           s = s & r(1, x) & vbCrLf
     Next
     MsgBox s
End Sub
 
what you mean that you hardly ever change arguments? chage what?

When I call a sub/function like this, which squares a number

Code:
function square (x as long) as long
    square = x * x
end function

I would have to call this in this way - so that the function does not change x, but returns a function result.
y = square x

Now you could do this, by just using the argument x directly. In this version the argument is changed to be the new value.

Code:
function square (x as long)
'x is by passed ref, so you can change it directly 
   x = x * x
end function

Now you don't need a variable y. You can just say
square x and now x is changed to its square.

That's what I hardly ever do - change a value of an argument within a sub/function, because I was brought up to believe this was a "bad thing" to do.

The real issue is that with a byref argument you could inadvertently change an argument within a sub without realising it, and then find you get unexpected errors in your programme. If the above function changed the value of x to its square, but something else in the function failed, then the new value of x may not be safe to use.

If you always use byval, you won't get unexpected errors of this nature - you have to consciously declare the argument as byref in order to change it. I suppose its 6 of one and half a dozen of the other, but its an ingrained habit that I find very hard to change - so I solve it by almost never changing a passed in argument. That's what the function result does.

I would only tend to change an argument in a function that returns a result as a sort of status. So if the function result is a boolean of true, then I might also have changed an argument to a new value. That's the only way I can get 2 results back from a function. If the boolean result is false, then I won't use the argument anyway, so it doesn't matter if it changed or not. That sort of thing.
 
Yes pretty much everything he said in that post is wrong. I can quote multiple sources. The one I provided is by Chip Pearson.
I am fully open to be proven wrong with an example where passing an object by val causes an issue. It really does nothing, but does not cause any issue.

See my strange result just above in #25- I thought exactly what Chip described, but passing byval seemed to work with no error, and not changing the passed in array.
 
We are confusing Value types and Reference Types. The @The_Doc_Man specifically called out objects and no mention of Arrays. Apples and Oranges. In VBA arrays are Value Types. So yes they are copied, that is not the same as reference types (objects).

 
We are confusing Value types and Reference Types. The @The_Doc_Man specifically called out objects and no mention of Arrays. Apples and Oranges. In VBA arrays are Value Types. So yes they are copied, that is not the same as reference types (objects).


In the post you referenced from Chip Pearson - the last item

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.

The MS link said the same thing.
Array argument must be ByRef | Microsoft Docs

I definitely had it in my mind that an array might be a large thing to copy, and was not passed by value - and I was surprised when my code seemed to successfully pass an array by value.
 
Last edited:
@gemma-the-husky
You are passing the array as a variant as described in that MS page.
So in this example I pass the array as a variant by value. Then pass it as an array by ref
Code:
Public Sub TestByVal()
  Dim Arr(2) As Integer
  Arr(0) = 10
  Arr(1) = 20
  Arr(2) = 30
  AddArr Arr, 5
  Debug.Print "byval"
  PrintArr Arr
  AddArr2 Arr, 5
  Debug.Print "by ref"
  PrintArr Arr
End Sub

Public Sub PrintArr(Arr() As Integer)
  Dim i As Integer
  For i = 0 To UBound(Arr)
    Debug.Print Arr(i)
  Next i
End Sub

Public Sub AddArr(ByVal Arr, val As Integer)
  Dim i As Integer
  For i = 0 To UBound(Arr)
    Arr(i) = Arr(i) + val
  Next i
End Sub

Public Sub AddArr2(Arr() As Integer, val As Integer)
  Dim i As Integer
  For i = 0 To UBound(Arr)
    Arr(i) = Arr(i) + val
  Next i
End Sub
results

I cannot write the second procedure to pass an integer array by val. It will fail
Code:
Public Sub AddArr2(Byval Arr() As Integer, val As Integer)
  Dim i As Integer
  For i = 0 To UBound(Arr)
    Arr(i) = Arr(i) + val
  Next i
End Sub
 
@MajP

Ah Yes I got it. I missed the significance of the error when I initially tried to use byval in the argument. I thought the error meant I was just defining the array argument incorrectly when I used your last construct. It wasn't incorrect at all - it was just illegal, and what I was doing was changing the data type to a variant as you pointed out, which makes it legal. I didn't appreciate the argument would not be of the same type as the variable that was being passed in!
 
@conception_native_0123 this may or may not be helpful to you - or it may just show that I am quite the simpleton in vba coding! :ROFLMAO:
And I am OK with that.

I've virtually never had a need or want to use anything other than the default, in many years of creating useful Access and Excel programs. Simply put, I never want a variable passed into a procedure to actually change its value and then continue its lifespan somewhere else.

If I want to test something and get a return value based on logic, I use a regular function, then assign the result of the function back to some variable in the calling procedure. I just don't get why there would be a whole lot of usefulness in doing the other.

Just one man's experience.
 
@Isaac
What you said is actually contradictory
Simply put, I never want a variable passed into a procedure to actually change its value and then continue its lifespan somewhere else. I use a regular function, then assign the result of the function back to some variable in the calling procedure
I agree with you, but that is not the default. As @gemma-the-husky pointed out you have to force yourself to specify byval (or be careful) so as not to change the value inadvertently in the called procedure.

The good news is that vb.net defaults to byval. I guessed they learned, because as pointed out calling a reference type byval really has not impact since you are just copying a pointer.
 
@Isaac
What you said is actually contradictory

I agree with you, but that is not the default. As @gemma-the-husky pointed out you have to force yourself to specify byval (or be careful) so as not to change the value inadvertently in the called procedure.

The good news is that vb.net defaults to byval. I guessed they learned, because as pointed out calling a reference type byval really has not impact since you are just copying a pointer.
Gotcha. Ok, then my erroneous implication stands corrected. Thanks for clarifying.

The default that I am using, then, actually IS the thing that can change the passed-in variable's value inside the called procedure, which I never want to do and would not find useful. I guess I have just always been careful/never tried to change its value.

This, then is a situation where I should do a better job of following my own oft-given advice, which is not to overly rely on 'defaults' but be explicit - if I had followed this advice, which helps people better understand what is happening in their code, I myself would have better understood what was happening in my code. LOL :p
 
If I want to test something and get a return value based on logic, I use a regular function, then assign the result of the function back to some variable in the calling procedure. I just don't get why there would be a whole lot of usefulness in doing the other.

that sounds like easiest way actually. yes i follow thank you
 
This, then is a situation where I should do a better job of following my own oft-given advice, which is not to overly rely on 'defaults' but be explicit - if I had followed this advice, which helps people better understand what is happening in their code, I myself would have better understood what was happening in my code. LOL
This is one of those areas where if you do not see it, you will drive yourself crazy trying to debug. It does not happen that often, but when it does it can be hard to see. Here is a silly example.

I have a function that tests if the passed in date is X amount of days in the future from today

Code:
Public Function BadCheck(dtmDate As Date, daysOut) As Boolean
  dtmDate = dtmDate - daysOut
  BadCheck = (dtmDate = Date)
End Function

Public Sub TestBadFunction()
  Dim OriginalDate As Date
  OriginalDate = #4/23/2021#
  Debug.Print "Original date " & OriginalDate
  Debug.Print "Is the date two days out? " & BadCheck(OriginalDate, 2)
  'Debug.Print "Original date " & OriginalDate
  Debug.Print "Is the date two days out? " & BadCheck(OriginalDate, 2)
End Sub

Results
Original date 4/23/2021
Is the date two days out? False
Is the date two days out? True

The first time it returns false because 4/23 is 4 days from know
The second time it returns true.
Well that is confusing until you figure out that the first time calling the function modified the value inadvertently. Obviously would never write a function exactly like that but there are cases when you mistakenly do.
 
The problem with ByVal being a default is that for objects, you start replicating things that are expensive to replicate AND the "instantiators" for the object don't necessarily replicate the values in those structure.

I was fighting tooth-and-nail with Excel and was sloppy about ByVal vs ByRef. I can tell you that Excel app objects REALLY don't like ByVal for anything other than the simplest variables.

And for that matter, Word simply WILL NOT BEHAVE if you use ByVal for things that aren't true scalar variables. If it COULD be a structure, ByRef is the ONLY way for it to work right.
Nearly 4 years later...

I was shocked today to find out ByRef was the default for passing an integer in VBA.
Just for the sake of the argument, as far as I remember C language, when passing an argument the value is copied, and the compiler determines weather the value is of an address (pointer, reference) or a 'regular' value.
When sending an object you send by default an address (pointer, reference), unless you want to send a copy, in that case you must create the copy (or have some create copy function.

why in the world would you send an integer as an address of an integer, in order to refer to it?
 
why in the world would you send an integer as an address of an integer, in order to refer to it?
Like I said previously, the Default, implicitly, is Byref.
Meaning you did not define in the Sub/Function explicitly

Example:
Code:
Public function fnX(x As Integer)
..
..
End Function
the variable x there will be implicitly Byref.
 
Just for the sake of the argument, as far as I remember C language, when passing an argument the value is copied, and the compiler determines weather the value is of an address (pointer, reference) or a 'regular' value.
The compiler "determines" nothing of the kind. Why would it? It's your job to define the function and with it the types and passing mechanisms for its arguments. - Which is the default behavior I don't know. I acquired the habit of explicitly stating the passing mechanism in the method declaration so I do not need to care about the default.

When sending an object you send by default an address (pointer, reference), unless you want to send a copy,
You always pass the address of an object. If you want to pass a copy of an object, you must create the copy and then pass the address of the copy.
 
why in the world would you send an integer as an address of an integer, in order to refer to it?
Here is a prime example where this construct is useful, because there is really no other way to do it.

Code:
Private Sub SomeObject_BeforeUpdate(Cancel As Integer)
    
End Sub

The application code that raised the beforeUpdate event passes the Cancel argument by reference. That way you can change the value of the Cancel argument and it is returned by to the application code that called it. That is how the application can read that you updated the value Cancel to false. You see this is many places in vba events procedures and other functions. There are other VBA functions or sub routines where you pass in Arguments and then you can read the updated values of the arguments modified by the procedure you called passed in besides just a single value returned by the function.
 

Users who are viewing this thread

Back
Top Bottom