Passing a class instance to a method ByRef

Mac: Sequoia
Xojo: 2024 Release 4.1

As I understand it, in Xojo, class instances are always passed by reference. I took this to mean that it was arbitrary as to whether or not you included ByRef or not.

I prefer a verbose coding style, so I tried writing a Method that had a class instance as one of the parameters

cnIsotropic is a subclass of Canvas that I have written.
The Method had two parameters: iPosition As Integer, ByRef some_cnvIsotropic As cnIsotropic

This results in an error: You can’t pass an expression as a parameter that is defined as ByRef

If I remove the word, ByRef, then the error goes away, and it works as expected. I am puzzled by this. I thought that presence or absence of ByRef would be irrelevant in that class instances are always passed by reference. Can somebody provide an explanation?

It’s not in the definition, it’s wherever you’re calling this method.

Using ByRef means you may change the source variable, so MyMethod(x) may result in x getting a different value. Because of this, you must pass the variable (or property) as a parameter, and cannot do this: MyMethod( FunctionThatReturnsX() ). But that’s what you are doing somewhere.

4 Likes

It’s not in the definition, it’s wherever you’re calling this method.

Ken you are correct here. The error is flagged when the code attempts to use the method.

Using ByRef means you may change the source variable, so MyMethod(x) may result in x getting a different value.

So you are saying that by using the verbiage ByRef I am implying that the Class Instance is modifiable in the Method? If I leave it out, then Xojo understands that it cannot be modified?

If I leave out the word, ByRef, I do not get an error, and it works appropriately. Nothing else is changed.

The purpose of including the class instance as a parameter is accessing some Methods of that instance in the code of the method that I have written. This seems to work just fine.

The instance is modifiable no matter what (assuming it can be modified anywhere). The difference is whether you want the variable that holds a reference to that instance to be modifiable.

Consider this code:

var c as new MyClass
c.Prop = 0

MyMethod( c )

Sub MyMethod(x As MyClass)
  x.Prop = 1
End Sub
// or
Sub MyMethod(ByRef x As MyClass)
  x.Prop = 1
End Sub

In either case, c holds the same instance with its Prop set to 1.

Now consider this:

Sub MyMethod(x As MyClass)
  x = nil
End Sub
// or
Sub MyMethod(ByRef x As MyClass)
  x = nil
End Sub

In the first version, c still contains an instance of MyClass with Prop set to 0. In the second version, c is now nil.

2 Likes

I wrote some sample code and observed what you state to be true.

Sub MyMethod(x As MyClass)
  x = nil
End Sub
// or
Sub MyMethod(ByRef x As MyClass)
  x = nil
End Sub

When I call MyMethod somewhere in my code the results are different.

Var c As New MyClass
c.Prop = 0
MyMethod(c)

c end up Nil when I use the second formulation of MyMethod - the one with ByRef.

I was puzzled because the Documentations says:

All arrays and any class types are always passed by reference regardless of what is specified in the method declaration.

I concluded from this that the use or non-use of ByRef in a method declaration would make no difference.

But as you said, and my experimentation confirmed in this simple case, it does make a difference.

As an addendum to Kem’s excellent example, it’s important to understand that due to compiler limitations, anything you pass into a ByRef must be a local variable. Consider these three examples:

Sub MyMethod(ByRef x As MyClass)
  x = nil
End Sub
Var C as New MyClass

MyMethod(C) //a local variable will work
MyMethod(Window1.C) //Window1.C is not local and will throw a compiler error
Var C as MyClass

C = Window1.C

MyMethod(C) //only the local variable will be changed

Recall that anything passed into a ByRef may have its value changed – so in the third example, only the local variable C will be set to nil in MyMethod. Window1.C will retain its original value.

ByRef is one of those absolutely necessary language features that will nonetheless trip up even the most experienced developer. You should only use it once you understand how it should be used, and only when necessary.

2 Likes

That’s a really good point, and the language is indeed confusing.

Variables such as integer, double, string, etc. are by default passed by value. These are called “intrinsics” because Xojo has the ability to copy their values. The method receives a copy of the value and the original variable’s value is not alterable from within the method.

Anything that is a reference – i.e. any object – is by default passing a reference. The method receives a copy of the reference to the object and can access it that way. This is done because Xojo has no built-in way to duplicate an object (as it does for intrinsics such as interger, et al). The original reference cannot be altered from within the method.

When you include the ByRef qualifier, you are telling the compiler to allow the code inside the method to alter the reference itself. This means that the code can completely replace whatever is pointed to by the reference, instead of just accessing it. In the case of an intrinsic, it means that the original variable may have a new value; in the case of an object reference, that reference may now point to a different object altogether.

It would indeed be better for comprehension if we had a different term for “passing a reference” versus “passing BY reference”. But here we are. :slight_smile:

5 Likes

Along with @Kem_Tekinay’s examples, this is a great summary of a subtle issue, thanks to you both.

1 Like

What prompted my original post was an error I got when trying to use a method that I had written. The error was

You can’t pass an expression as a parameter that is defined as ByRef

This was the line of code that prompted the error

objectToAdd = XSM.CreateSideStraight_G2D(iPosition, Self.cnvMainXS)

Self.cnvMainXS was an instance of a class in my project called cnIsotropic.

The only reason that the method CreateSideStraight_G2D needed the class instance Self.cnvMainXS was that it need to access a method of that class. I wasn’t actually changing any properties of Self.cnvMainXS in the method.

The original version of CreateSideStraight_G2D was written with the second parameter being – ByRef some_cnvIsotropic As cnIsotropic. Floundering around trying to figure out the meaning and cure for the error

You can’t pass an expression as a parameter that is defined as ByRef

I eliminated the ByRef in the method and the error went away and things just worked. I was puzzled because I thought that ByRef in this context would have no impact.

Anyway, the contributions of Ken and others made me aware that the entire topic was a lot more complicated than I had understood. I will now avoid throwing in ByRef unless specifically needed. In my case, it caused a confusing error that I still do not understand, but I do not need in my life.

Thanks to all. I will credit Ken with the “Solution” becuase he responded quickly and immediatrely disabused me of my simplistic understanding. But others were also helpful.

anything you pass into a ByRef must be a local variable

That is a gem that is helpful to me.

1 Like

Other languages do this differently, for example in Swift you label a function parameter with inout to indicate that the value can be changed inside the function and changes propagate outside of the function.

See https://docs.swift.org/swift-book/documentation/the-swift-programming-language/declarations#In-Out-Parameters

// SWIFT code
func someFunction(a: inout Int) {
    a += 1
}

var x = 7
someFunction(&x)
print(x) // Prints "8"

Edit to add: In Swift, you also have to use the & (ampersand) operator when calling the function.

1 Like

Much less intuitive than “byRef” imo.

One of my biggest issues with Swift, along with significant whitespace (ugh) - cryptic stuff like this.