Class loses properties after method call

The situation as I understand it:

  • in some cases, the compiler will take an assigment like this:
    ClassAinstance = ClassBinstance and if there is a way to Operator_Convert() from one to the other via an intermediary class, it will do so.
  • but only sometimes. For certain intermediary types it works, not for others.
  • this intermediate instance is never visible in the debugger (since it’s created and destroyed instantly)
  • if you aren’t aware of this, it’s rather confusing, as it appears that your instance was improperly Cast from A to B and lost information in the process.
  • what actually happened is that your instance was converted from A to Z then converted from Z to B again, losing information in the process.

Additional complexities:

  • Operator_Convert() can exist in a superclass, but create a new instance of a subclass.
  • Operator_Convert() does not call the Constructor
  • This makes it very tricky to use.

As a possible workaround:

  • I have tested, and a subclass can override the Operator_Convert() that exists in the superclass
  • so if you knew this was going on, you could add the Operator_Convert() to the Subclass, at which point you’d have a better chance of debugging…

Perhaps a comment from Xojo would be welcome.

I’ve been trying to see if I can “fix” this by adding an Operator_Convert method in @Anthony_G_Cyphers example posted upthread. I’ve tried adding it to TestClass and OtherTestClass, but it’s never called. I find the doc for this, confusing.

Class A

    Public Var value As Integer

    // Surely this should be avoided if not forbidden
    // Because it causes unexpected bad behaviors
    Private Function Operator_Convert As A
        Var r As New A
        r.value = self.value
        Return r
    End Function

End Class

Class B

    Public Var value As Integer

    // This should be a correct explicit way of cloning it
    // and preserving the expected behavior
    Public Function Clone() As B
        Var r As New B
        r.value = self.value
        Return r
    End Function

    Public Function Constructor(o As B) As B
       Return o.Clone()
    End Function

End Class


//----- So, what's expected ?

Var p As New A
p.value = 1

Var q As A = p
// It's expected p and q pointing to the same obj
// But instead, the q object was hideously changed under the hood
q.value = 2

// And now p.value and q.value are not the same as expected
// but without knowing the implementation both should be the same


// The B class fix it

Var x As New B
x.value = 1

Var y As B = x // x and y points to the SAME object
y.value = 2

// And now x.value and y.value are the same: 2

// What if I want a clone? As A() does?
// Use the proper cloning tools as cloning methods or constructors

y = x.Clone()
// y.value = 2 at this point

y.value = 3

// And now x.value and y.value differs as expected, they are 2 and 3

1 Like

@Rick_Araujo : your example and the original example are a little different:

  • the original example, A and B are both subclasses of a common class (Rect) which has an operator_convert() to itself (Rect)
  • Your example: A and B do not share a common subclass that has Operator_Convert(), but class A has Operator_Convert() to its own class (A).

Both situations are weird and dangerous, and I do wonder if they should be prevented (or at least trigger a compiler warning?)

My A() simulates what Rect() currently is, a Class with a hidden Operator_Convert messing around.

My B() is what a Rect should be in such situation, something that needs a

y = x.Clone()

or a

y = New B(x)

To make a new copy

I think it should default to an Error that a #pragma could change to a Warning or disable as:

Class A

    Public Var value As Integer

    #Pragma DirectConvertAssignment Warning  // Allows the Operator_Convert below but warns
                                             // Values = Error(*), Warning or Allow
                                             // (*) Default
    Private Function Operator_Convert As A
        Var r As New A
        r.value = self.value
        Return r
    End Function

End Class
Class C

    Public Var value As Integer

    // Without #pragma this will rise an Error asking to use proper cloning designs
    Private Function Operator_Convert As C
        Var r As New C
        r.value = self.value
        Return r
    End Function

End Class

So using the above code, with DirectConvertAssignment set as Warning level, the compiler should emit:

// A warning during the A Class definition as:

 #Pragma DirectConvertAssignment Warning  // Allows the Operator_Convert below but warns
                                             // Values = Error(*), Warning or Allow
                                             // (*) Default

Private Function Operator_Convert As A // <-- Warning here

Warning - Direct assignment conversion should be avoided, maybe you should prefer to use prototype (cloning) designs?


// A warning during the A Class object assignment

Var x, y As A
x = New A
y = x // <-- Warning here

Warning - y (Class A) implements a direct assignment conversion, consider a redesign using constructors or methods like .Clone()


The warning level is great to keep legacy code running while a redesign is being made. The warnings will help to locate places needing attention.

Another thing, based on what Greg said (they wanted to emulated a struct for rect, but using a class) but doing so they created a breach to bugs related to sub-classing it. So, one way to avoid such things would be just forbidding subclassing it implementing the final keyword.

So a finalized class (called sealed in C#) could be like

Final Class A

    Public Var value As Integer

    #Pragma DirectConvertAssignment True
    Private Function Operator_Convert As A
        Var r As New A
        r.value = self.value
        Return r
    End Function

End Class

That would accept the hidden “cloning” directly using “conversion” without warnings.

But would forbid what will likely cause a mess in the future, subclassing it.

Class D Inherits A // <-- Error

    Public Var value2 As Integer

End Class

Error: Class A is Final and can’t be subclassed/extended

Closed as By Design with a documentation update.
https://tracker.xojo.com/xojoinc/xojo/-/issues/68583
https://documentation.xojo.com/api/graphics/rect.html#notes

Buggy design…

-Peter, that’s a nasty bug!
-Nooo. That’s just an inconvenient weird feature you guys need to workaround.

Please,change the “By Design” to something else, like “Won’t change” without explanations. Such thing “By Design” may kill Xojo reputation.

3 Likes