by reference (1 Viewer)

I see this has sprung up again. However, I have to admit when I have had mistaken impressions. I went through the VBA language reference and found some things in the chapter on processing of function/subroutine arguments.

It IS possible to formally declare something ByVal and actually pass in an object. But the catch is that the object must return a value when invoked, because a Let-assign will occur during run-time processing of the invocation. That way, the object's .Value property is evaluated for the Let-assign to the formal (ByVal) parameter/argument. If the object doesn't return a value, you can't do a Let-assign. In that case you get a COMPILE-TIME error, either a Type Mismatch or an Invalid Parameter error. Which is why you CAN pass in something like function DATE(NOW) as an actual parameter to ByVal variable. If the variable that would be the formal parameter cannot accommodate the .Value of the actual parameter, you would get a run-time error of Type Mismatch.

I stand by the fact that I got anomalous results (but not compile-time errors) at places I might have expected when dealing with some Excel and Word internal methods, but acknowledge that it could have been something getting de-referenced unexpectedly somewhere between Access and Excel or Access and Word. It is DEFINITELY a PITA environment when trying to do something tricky.
 
It IS possible to formally declare something ByVal and actually pass in an object. But the catch is that the object must return a value when invoked, because a Let-assign will occur during run-time processing of the invocation. That way, the object's .Value property is evaluated for the Let-assign to the formal (ByVal) parameter/argument.
Of course you can declare an object variable ByVal. - However, this will only pass the pointer to the object by value (as a copy), not the object itself. - It is not possible to automatically "copy" an object. You would need custom code tailored to the particular type of object to copy an object.

I don't understand your comment re "the object's .Value property". - This seems completely irrelevant to me. Many objects do not have a .Value Property. You probably mean the default property of the object, but still I don't see any reason why that would be invoked by just passing the object to a procedure; regardless whether it's ByVal or ByRef.
 
Of course you can declare an object variable ByVal. - However, this will only pass the pointer to the object by value (as a copy), not the object itself. - It is not possible to automatically "copy" an object. You would need custom code tailored to the particular type of object to copy an object.

I don't understand your comment re "the object's .Value property". - This seems completely irrelevant to me. Many objects do not have a .Value Property. You probably mean the default property of the object, but still I don't see any reason why that would be invoked by just passing the object to a procedure; regardless whether it's ByVal or ByRef.

The comment you question specifically refers to passing in a complex entity via a scalar value declared ByVal. You can do that if and ONLY IF the non-scalar entity RESOLVES to a scalar value, i.e. it has a .VALUE property that can be retrieved (probably using a property GET method) OR it returns a value via the standard method for functions to do so.

My example was to pass in a DATE(x) function, which can be done by value if the corresponding formal argument/parameter is compatible with a DATE data type or the date value can be coerced into the data type. For instance, you CAN coerce a date data type via LONG(date) - because Access doesn't allow you to have dates later than 31-Dec-9999, and that still fits into a LONG. But you could NOT coerce current dates into an BYTE because that would overflow.

You can't pass in a function as a parameter ByRef and expect it to do anything on return. The answer gets lost on return from the call. That is NOT the case for a scalar variable being passed ByRef.
 
The comment you question specifically refers to passing in a complex entity via a scalar value declared ByVal. You can do that if and ONLY IF the non-scalar entity RESOLVES to a scalar value, i.e. it has a .VALUE property that can be retrieved (probably using a property GET method) OR it returns a value via the standard method for functions to do so.
That is a really confusing way to say that the compiler will do a good job of resolving default properties to meet a procedures signature. And your explanation is extra confusing since it has nothing to do with by val or by ref, and just the signature of the called method.

Example
Code:
Private Sub cmdTest_Click()
  PassObj1 Me.txtBx1
  PassObj2 Me.txtBx1
End Sub

Public Sub PassObj1(ByVal obj As Object)
  MsgBox TypeName(obj)
  MsgBox obj  'Msgbox resolves the object
  MsgBox obj.Name
End Sub

Public Sub PassObj2(ByVal obj As String)
  MsgBox TypeName(obj)
  MsgBox obj
End Sub

Two methods supposedly pass the same thing, but not really.
1. In the first case the textbox is passed as an object of type name textbox. Or better yet a copy of the pointer is passed.
2. In the second case the compiler is smart (or too smart) to know to resolve for you the default Value property and pass the value of the textbox. Or in this case a copy of the value. The obj is not passed as you say, but it is resolved before passing.
Nothing to do with by Ref or by val
 
That is a really confusing way to say that the compiler will do a good job of resolving default properties to meet a procedures signature. And your explanation is extra confusing since it has nothing to do with by val or by ref, and just the signature of the called method.

Example
Code:
Private Sub cmdTest_Click()
  PassObj1 Me.txtBx1
  PassObj2 Me.txtBx1
End Sub

Public Sub PassObj1(ByVal obj As Object)
  MsgBox TypeName(obj)
  MsgBox obj  'Msgbox resolves the object
  MsgBox obj.Name
End Sub

Public Sub PassObj2(ByVal obj As String)
  MsgBox TypeName(obj)
  MsgBox obj
End Sub

Two methods supposedly pass the same thing, but not really.
1. In the first case the textbox is passed as an object of type name textbox. Or better yet a copy of the pointer is passed.
2. In the second case the compiler is smart (or too smart) to know to resolve for you the default Value property and pass the value of the textbox. Or in this case a copy of the value. The obj is not passed as you say, but it is resolved before passing.
Nothing to do with by Ref or by val

If you think MY comment was somewhat obscure, try reading the MS VBA Language Reference, somewhere around section 5.2 that deals with subroutine/function activation with formal arguments. I did my best to deconvolute what they said. Obviously, not well enough. But I was still trying to correct / acknowledge my previous error. Give me a little credit for THAT much, at least.
 
what you mean that you hardly ever change arguments? chage what?
I know this is an old thread, and I may have replied already, but what I meant was this.

Because I was so used to passing arguments to procedures byval, and not being able to pass amended values back to the calling code, I just developed a habit of never trying to change them within the procedure even when they are passed in byref (by default). Occasionally, I do modify an argument, but in truth I am more likely to use a function to return a value, rather than change the passed in argument. As an exception if I want to return a function result and ALSO change a passed in value as well, then I know I can do that, but it's very infrequent.

as @MajP said, I also understood that even if you try to pass some variables (objects) byval, they actually get passed in byref.
 
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.

It really does nothing, but does not cause any issue.

Well it might cause an issue. If you pass something to a procedure explicitly byval, and expect it not to be changed by the procedure, and then continue to use that thing expecting it not to have changed, it may well cause an issue if the procedure changed the original thing because even you asked for it passed byval, it was actually passed byref. I assume that's what @The_Doc_Man was referring to.
 
If you pass something to a procedure explicitly byval, and expect it not to be changed by the procedure, and then continue to use that thing expecting it not to have changed, it may well cause an issue if the procedure changed the original thing because even you asked for it passed byval, it was actually passed byref. I assume that's what @The_Doc_Man was referring to.
That is a very strange argument to say it "might cause a problem" if you incorrectly assume something would work a certain way and it does not work in the incorrect way. It never occurred to me that someone would assume that passing an object by val would create a Deep Clone / Copy of the object. To me that was implied here, and thus why I was so confused.
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.

So there is a difference, but I cannot think of a real world example where it would make a difference in usage. From the MS site explaining the difference.

ByVal ByRef

Reference type (contains a pointer to a class or structure instance)The procedure cannot change the variable but can change members of the instance to which it points.The procedure can change the variable and members of the instance to which it points.

I found this demo showing what that means
Code:
Private Sub Form_Load()
    Dim z As Object
    Set z = New Collection
    z.Add "Dog"
    z.Add "Cat"
    
    foo z
    ' // z is still collection
    Debug.Print TypeName(z)
    Dim I As Integer
    For I = 1 To z.Count
      Debug.Print z(I)
    Next I
    
    bar z
    ' // z is form now
    Debug.Print TypeName(z)

End Sub
Private Sub foo( _
            ByVal cObj As Object)
     cObj.Add "Frog"
     cObj.Add "Bird"
     Set cObj = Me
End Sub

Private Sub bar( _
            ByRef cObj As Object)
    Set cObj = Me
End Sub

This creates a collection and adds some members. Then it passes it byVal to procedure Foo. Since this is a copy of the pointer when you set cObj to Me (the current form) it does not impact the variable Z. You can see that Z is still a Collection. When you pass it ByRef to Bar and set cObj to Me the original pointer now points to the current form. So Z is now a Form.
Code:
'Z remains a collection
Collection
Dog
Cat
Frog
Bird

'Z points to the current form
Form_Form_all_tables


Maybe this is a little more problematic in VBA since there is no easy way to do any cloning shallow or deep. When writing your own classes you can build in a copy method to make either a shallow or deep clone (assuming you create for each sub class of a composite class.).
 

Users who are viewing this thread

Back
Top Bottom