Advance Class: Is there a way to return an object of the kind of the class without knowing upfront.

I am working on an active record project. I am specifically writing the .find method, where you pass a query. So you will say:

Person.find(“lastname=10”)

Here a query will be run that will get a list of objects of type person who’s query match the find parameter.

All is good, but I want to write this in a super class that can become anything (a model). So my base class is called KLActiveModel and it has a method that can parse the recordset cursor to the properties, however in this case I am trying to create an array of subclasses from a super class… (An array of Person objects in the KLActiveModel) however the superclass has no way of knowing the type of class the subclass is.

What i want is instead of returning from the subclass a KLActiveModel() array i want to return a ‘WhateverTheSubclassTypeIs()’

I hope I am explaining myself, basically I want to return an array of objects of the kind the instance is… Kind of returning an (id) array in objective-c, but in OC casting will work… Not in Xojo.

Any clues welcomed!

What you’re describing is a class interface. See the User Guide, starting on page 141.

Thank you for the answer Andrew,

Maybe it can work, but I don’t think I explained my self clearly, because how can I create an instance of a “Class Interface” in a class that does not know anything about the subclass.

What I need is something like :

“Dim X as new ObjectOfTypeSelf”
ObjectOfTypeSelf being the type of the subclass… And it will be implemented in a method in the super class…

or

“Dim W as NewinstanceOfSelf” or something equivalent…

This is a fairly new concept in Obj-C maybe its none existent in Xojo ;(

The only way I know to do this in Xojo is with a class interface. If all subclasses implement the same interface, then the superclass can treat them all as instances of the interface, with the restriction that only the methods of the interface will be available to the superclass.

To communicate from a super class to it’s possible subclass use a new Event.

[code]Class MySuperClass
Function getNewSubClassInstance() As MySuperClass //Event Definition
Sub superClassMethodThatWantsNewInstanceOfTypeSelf()
dim X As MySuperClass = RaiseEvent getNewSubClassInstance
End Sub
End Class

Class MySubClass inherits MySuperClass
Function getNewSubClassInstance() As MySuperClass //Event Handler
return new MySubClass
End Function
End Class[/code]

Each subclass will need to fill in this event.

You mean you want the returned array to be typed as WhateverTheSubclassTypeIs? That won’t work (unless you return Variant) but you can pass in a subclass array to a superclass typed parameter. Not sure if this is what you’re trying to do or you intend for a polymorphic array.

I think it’s also possible to use Introspection to instantiate new instances of the full self type.

Thanks for all the answers… I will explore all options…

Doofus: do you know how to do that with introspection?

Almost. Get the ConstructorInfos from the TypeInfo of self. Then you need to find a 0-parameter constructor to invoke, and that’s the tricky part. If there’s 0 param constructors on both super and sub then the ConstructorInfo array has 2 matches and I don’t know how to distinguish them. Using either one will instantiate the correct type but it could be the supers constructor that’s invoked.

Using an event mechanism allows each subclass to customize how it should be created for this specific task. Introspection will need this constructor choice worked out to be safe.

[code]Class Class1
Function makeMoreOfMe() As Class1
dim ca() As Introspection.ConstructorInfo = Introspection.GetType(self).GetConstructors
for i As integer = 0 to ca.Ubound
if ca(i).GetParameters.Ubound = -1 then return ca(i).Invoke
next
return nil
End Function
End Class

Class Class2 inherits Class1
End Class

//Pushbutton
Sub Action()
dim a As new Class2
dim b As Class1 = a.makeMoreOfMe
break //b IsA Class2
End Sub
[/code]

All right! So thanks to all your help, I decided to go the introspection way. It is the most automatic for my daily use. Here’s what I am down to :

[code]Dim Product as new Product //Instantiate an empty KLActiveModel sub class, that holds properties for my model (Product)
Dim ProductArray() as KLActiveModel = DeliProd.Find(“description like ‘%My Description%’”) //Returns all matches in an array

MsgBox Product(ProductArray(1)).productName //Show’s the description of Product array…
[/code]

Any other wizard out there with a solution to skip the casting —> Product() So it can look like this:

MsgBox ProductArray(1).ProductName //Since this is a method implemented in KLActiveRecord (Product Superclass) I have to return a generic placeholder.. And then cast it...

Here’s the find method in KLActive Record:

  Sub Find(WhereQuery as string) as KLActiveRecord() 
  Dim RS as RecordSet
  Dim DBModels() as KLActiveRecord
  
  RS = DBConnector.SQLSelect("Select * from " + self.getTableName + " where " + WhereQuery)
  
  If rs = nil or rs.RecordCount <=0 then
    System.DebugLog("[KLActiveRecord] No record found for query : " + WhereQuery)
    Return nil
  end if
  
  
  While NOT RS.EOF
    Dim DBM as DBModel 
    DBM = self.NewInstance
    DBM.setModelWithRecordSet(RS)
    DBModels.Append(DBM)
    Rs.MoveNext
  Wend
  
  
  Return DBModels
End Sub

And here’s the NewInstance Method in KLActriveRecord

[code]Sub NewInstance as DBModel
dim ca() As Introspection.ConstructorInfo = Introspection.GetType(self).GetConstructors

for i As integer = 0 to ca.Ubound
if ca(i).GetParameters.Ubound = -1 then return ca(i).Invoke
next

return nil
End Sub[/code]

I am happy with this so far, but it would be cool not to have to cast it! :wink: But in my head seems impossible?

I’ve run into this before, the problem is you want to return a polymorphic array and cast the array down once. But arrays can only be cast up, sub to supers.

dim a() As MySuperClass dim b() As MySubClass a = b //allowed implicit casting b = a //not allowed

So instead of trying to downcast a returned array you can pass in an upcasted array

[code]//Change Find to take the array as parameter

Sub Find(WhereQuery as string, DBModels() as KLActiveRecord)

//In your code dim the array as the correct type and pass it in

Dim Product as new Product
Dim ProductArray() as Product
DeliProd.Find(“description like ‘%My Description%’”, ProductArray)
MsgBox ProductArray(1).productName[/code]

Another technique I’ve used is to create a utility method that performs a downcast.

Sub downCast(src() As Object, dest() As Object) dim last As integer = src.Ubound redim dest(last) for i As integer = 0 to last dest(i) = src(i) next End Sub

(Will error if types don’t match.) Your code then looks like…

[code]Dim Product as new Product
Dim ResultArray() as KLActiveModel = DeliProd.Find(“description like ‘%My Description%’”)

Dim ProductArray() As Product
downCast(ResultArray, ProductArray)

MsgBox ProductArray(1).productName[/code]

Both stategies have their tradeoffs but afaik there’s no direct downcasting of arrays in xojo. Upcasting relies on being implicit, downcasting I think would need to be explicit. What syntax?
dim a() As MySuperClass = subClassInstance.find(query)
dim b() As MySubClass = MySubClass()(a)

Perfect, I think that passing the array will be a good solution given what the framework is designed to do…

Thank you for all the insight!

@doofus: thanks for the nice workaround idea to downcast arrays. You may want to add the following Feature Request to your favorites: <https://xojo.com/issue/24167> Allow casting of arrays
I have added your snippet there as well.

A challenge with casting arrays is Xojo has no syntax for it.

dim ta() As MyClass = MyClass()(objArr) //cast array?
or
dim ta() As MyClass = MyClass(objArr)() //cast array?

Somehow the () need to be in there to fully designate the type. Or maybe they could overload CType to take a String for datatype…

dim ta() As MyClass = CType(objArr, “MyClass()”)

Though that might lose compile time checking that MyClass() is a real type.