Search code examples
excelvbapass-by-referencepass-by-value

Is ByVal a lie?


I think I've recently (finally) started to get to grips with how ByVal and ByRef work in VBA.

Observations

In all the tutorials and references I've been through to date, ByRef was always "passing the object", ByVal was "passing a copy of the object". To me the latter meant the object's place in memory was duplicated and a pointer to that new location was returned. I now realise that's not always the case, in fact as far as I can tell it's rarely the case: instead, most objects and classes are in fact passed ByRef even with ByVal specified in a routine's signature.

A System.Collections.ArrayList is passed silently ByRef as demonstrated in this code:

Sub test()
    Dim list1 As Object, list2 As Object
    Set list1 = CreateObject("System.Collections.ArrayList")
    list1.Add "foo"
    Set list2 = RemoveItem(list1)
    Debug.Assert list2.Contains("foo") = False 'as expected
    Debug.Assert list1.Contains("foo") = True  'raises error, meaning list1 was passed byref not byval
End Sub

Function RemoveItem(ByVal list As Object) As Object 'ByVal
    list.Remove "foo" 'expect to remove from a copy and return that
    Set RemoveItem = list
End Function

That surprised me given what I thought I knew about ByVal. Further digging shows me that to get a copy like I want from ByVal I need the objects I'm passing to have a method that enables this. For an ArrayList, the .Clone method makes a shallow copy. So my function becomes:

Function RemoveItem(ByRef list As Object) As Object 'ByRef or ByVal, makes no difference
    Dim listCopy As Object
    Set listCopy = list.Clone 'make a shallow copy of the object
    listCopy.Remove "foo" 'actually remove from a copy and return that
    Set RemoveItem = listCopy
End Function

A VB Array raises a compiler error when passed ByVal, perhaps as a warning against just this

Questions

All that got me thinking:

  1. What is the difference (if any) between ByVal and ByRef for classes/objects
  2. What distinguishes certain types (Boolean, Long) which can be passed ByVal from classes and types that can't
  3. In terms of library references - things that may not be written in VB6
    • Is it possible to create an object with a default ByVal response?
      • In Python you can specify how a class reacts to different keywords. I doubt you can do that natively in VBA, but could an object written in a more versatile language ever say, detect if it is being passed ByVal and return a .Clone of itself.
      • In other words, how do VBA's built-in datatypes get passed ByVal and can any other objects mimic this behaviour (again, in Python, everything's an object so everything's behaviour can be copied*). Or do I not have to worry about this ever happening?
    • Is it possible to mimic an Array's behaviour and raise a compiler error when passed ByVal - as an array has no clone method and cannot be readily copied so is always ByRef
  4. Is there a Sub, Function or method that will let me create deep-copies of objects? That is, a generic way of duplicating the memory one object uses, and creating a second copy of an object.

*anecdotal, I'm very new to Python


Solution

  • In all the tutorials and references I've been through to date, ByRef was always "passing the object", ByVal was "passing a copy of the object".

    Do you have a link for an example of the above?

    1. For objects (created from classes) ByVal is like *foo whilst ByRef is like **foo.

    2. Boolean and Long are primitives, for primitives ByVal is like bar whilst ByRef is like *bar.

      • no
        • there is no way for a object to tell if it has been passed ByRef or ByVal.
        • VBA 'built-in' datatypes have analogues in C/C++ so Long is a 32-bit integer so is Boolean in fact but can only take one of two values (0=False,-1=True). You need not worry, VBA places limits in the name of safety.
      • If you want to enforce passing ByRef for your objects then create a Type instead of a class.
    3. For classes, you will have to write your own copy constructors both shallow and deep. But Types can be copied just by using = both shallow and deep.

    Arrays and Types are passed ByRef because they are created on the stack.