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…
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
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