Folks, Does Clarion support the concept of CASTing in OOP? In C# I might declare a Base Class and use that for my Derived classes but store only references to the Base Class and then cast the data to access methods etc at runtime. Or does Clarion to this implictly?
Alas, I don’t know C# well enough to make a direct comparison, but here is what Clarion does (so hopefully this answers your question).
Let’s say I have a class
And I use this class as a pointer or parameter
bruce procedure(StringTheory pStr) other &StringTheory code
Then I can pass in any class derived from StringTheory as the parameter, or assign to the pointer.
BigBang Class(StringTheory) Explosion Procedure() End code Bruce(BigBang)
The only limitation is that the Bruce function cannot access the new methods or any new properties, because they are not part of StringTheory. So;
bruce procedure(StringTheory pStr) other &StringTheory x long code x = pStr.records() ! legal pStr.Explosion() ! not legal
Thanks Bruce, but not quite what I was thinking.
In your example above, you would need to tell the compiler that you want to treat your pStr as a BigBang not a StringTheory. But you have told the procedure to accept a StringTheory so I understand why ! Not Legal would be correct.
In C# (or C++) you could create for example a queue of ANIMALS. You don’t care in the queue what sort of animals they are but they are of the base type ANIMALS.
During runtime I could iterate over that list of ANIMALS and do something like:
MyAnimal = GET(ANIMALS) ! Get next Animal from the QUEUE
If MyAnimal IS A DOG then (DOG)MyAnimal.Woof
If MyAnimal IS A CAT then (CAT)MyAnimal.Weow
CAT and DOG are derived ANIMALS and the (DOG) or (CAT) casts the MyAnimal to make the compiler that it as a DOG or CAT object/class.
The reason for this is that you can store different types of things in a queue or list but as they are all just ANIMALS (because that is the base class) you can treat them as ANIMALS or CAST them to what they really should be.
From what I can see, Clarion doesn’t support this. It supports ANY but then again I can’t cast an ANY object to the type of Object I want it to be.
I guess what I could do is (thinking out loud) and assuming that the ANIMALS base class had a IsA property then could I:
I_AM_A_DOG PROCEDURE(DOG pDog)
I_AM_A_CAT PROCEDURE(CAT pCAT)
MyAnimal = Get(ANIMALS)
If MyAnimal.IsA() = ‘DOG’ then I_AM_A_DOG(MyAnimal)
Would this then be treated as a DOG in the I_AM_A_DOG class or would the compiler not like me passing an ANIMALS to something that wanted a DOG?? ??? Need to go and try this.
No you can’t CAST in Clarion like in C#. Use virtual methods instead.
Another approach of “casting” (I don’t recommend this though):
Test PROCEDURE(TAnimal pAnimal) thisDog &TDog thisCat &TCat CODE CASE UPPER(pAnimal.GetName()) OF 'DOG' thisDog &= ADDRESS(pAnimal) thisDog.Woof() OF 'CAT' thisCat &= ADDRESS(pAnimal) thisCat.Weow() ELSE MESSAGE('Unknown animal!') END
As @Mike_Duglas pointed out, there is a way to UPCAST in clarion, but it relies on you finding a way to implement your own IS A logic.
This sort of coding will usually lead to maintenance headaches, which is to say they are a bad idea ( a code smell ). I too would encourage you to find a different way to accomplish what you’re writing.
Also as pointed out by @Bruce you can easily DOWNCAST in clarion, however that too has some nuance. If you call a method on the downcast object, then derived methods are not called unless they are ,VIRTUAL in the downcast object. This holds true for the DESTRUCT method, so disposing of a downcast object can lead to memory leaks.
It didn’t seem 100% clear to me that what Mark was saying is making Destruct VIRTUAL prevents this leak of stuff the Derived class needs to Dispose() so Destruct should always be VIRTUAL.
Say you had a Truck Class derived from Vehicle that needed it’s own Queue which is Disposed in the Destructor:
Truck CLASS(Vehicle) , TYPE CONSTRUCT Procedure() ! New(TruckQueue) Truck specific data DESTRUCT Procedure() , VIRTUAL ! Dispose(TruckQueue) Truck specific data TruckQueue &TruckQueueType END
Typical code only references the Truck class so a DISPOSE(Truck) will fire the Truck DESTRUCT without Virtual.
The problem comes if your code references the Truck as the Vehicle base class. If you Dispose that Vehicle Reference the Truck DESTRUCT will not fire without VIRTUAL. A way I’ve seen this is a Queue of &VehicleClass references that can be any Derived type like a Truck. To cleanup each Queue record class reference is disposed and they are &VehicleClass references so need Virtual to call the Truck.Destruct().
I learnt that one the hard way.