If you have a string that identifies a class name, Such as “Contact”, and you have three string arguments, say name, phone, email; then is it possible at runtime to dynamically instantiate an object of the class identified by the class name?
Thus I’d like to do the equivalent of
Dim aContact as New theClass( name, phone, email)
where theClass is a variable containing the string “Contact” and the class Contact is known to have a constructor of 3 string arguments.
However I am curious if there is some other mechanism for doing this, such as done by object serialization/de-serialization programs for Xojo, of which there are several, but for which I have no understanding.
In .NET we call this Reflection. Xojo calls it Introspection. Generically, it is called late-binding or runtime binding. Look at the docs for Introspection. I haven’t used it yet, but offhand it looked to me like it has everything you need to do what you’re asking.
I didn’t do reflection often in .NET either but when I needed it, it was a life-saver. In one instance I needed to look up the concrete class name from a DB table and instantiate based on that – and on the ID of the business customer. In another, more recent situation with a mortgage company, I had to work around an epically bad plug-in design so that I could get about 30,000 lines of code out of a single source code file and break it down into maintainable chunks.
Fine, I’ll buy that. But the question comes up of how deserialization from JSON or xml works. Granted you create an instance of a new top-level object you want populated; but then the population must occur; presumably having saved the known type-info for the various object classes you previously serialized.
We’ve done it in the past with a Factory method and an interface. But that was a while ago and there’s probably more advanced ways of doing it. You may want to look at Einhugur’s serialization framework.
Create and instance of the object and store in a dictionary and then use introspection to create a new one in code
//Initialise all classes you want to create in code
App.ClassDictionary = New Dictionary
App.ClassDictionary.Value("Contact") = new Contact("Name", "Phone", "Email") //Data doesnt matter on the stored one..you could pass empty strings
Create this method - the one that will be used to create other class instances. It currently only checks if the constructor param length matches. You could also pass through another variable into this method “Optional ParamTypes() As String” and loop through to make sure the passed data matches the data types in “ciparam”.
Public Function InitialiseClass(ClassObject As String, Optional Params() As Variant) As Variant
Dim ti As Introspection.TypeInfo
Dim ClassObj As Variant
If App.ClassDictionary.HasKey(ClassObject) Then
ClassObj = App.ClassDictionary.Value(ClassObject)
End If
If Not IsNull(ClassObj) Then
ti = Introspection.GetType(ClassObj)
If Not IsNull(ti) Then
Dim ci() As Introspection.ConstructorInfo = ti.GetConstructors
For i As Integer = 0 To ci.LastIndex
Dim ciparam() As Introspection.ParameterInfo = ci(i).GetParameters
If IsNull(ciparam) And IsNull(Params) Then
Return ci(i).Invoke()
ElseIf (Not IsNull(ciparam) And Not IsNull(Params)) And ciparam.LastIndex = Params.LastIndex Then
Return ci(i).Invoke(Params)
End If
Next
End If
End If
Return Nil
End Function
NewClass will be the Contact object with the passed fields
Dim NewClass As Variant
Dim Params(-1) As Variant
Params.Add("TestName")
Params.Add("TestNumber")
Params.Add("TestEmail")
NewClass = InitialiseClass("Contact", Params)
Edit: Added some error checking and replaced the AppData method with code
Very true Bob as I too also needed this two days ago on a project where I wanted to have an automatic way to collect all base classes that are added to a module and instantiate at runtime for a ruleset class design using an OCP approach. The OCP guide I was working through was .NET and was using reflection functions we don’t have with introspection. I worked around it and devised another dynamic design, but its not as clean.
Public Function InitialiseClass(ClassObject As String, Optional Params() As Variant, Optional ParamTypes() As String) As Variant
Dim ti As Introspection.TypeInfo
Dim ClassObj As Variant
If App.ClassDictionary.HasKey(ClassObject) Then
ClassObj = App.ClassDictionary.Value(ClassObject)
End If
If (IsNull(Params) And IsNull(ParamTypes)) Or _
(Not IsNull(Params) And Not IsNull(ParamTypes) And Params.LastIndex = ParamTypes.LastIndex) Then
If Not IsNull(ClassObj) Then
ti = Introspection.GetType(ClassObj)
If Not IsNull(ti) Then
Dim ci() As Introspection.ConstructorInfo = ti.GetConstructors
For i As Integer = 0 To ci.LastIndex
Dim ciparam() As Introspection.ParameterInfo = ci(i).GetParameters
If (IsNull(ciparam) Or ciparam.LastIndex = -1) And (IsNull(Params) Or Params.LastIndex = -1) Then
Return ci(i).Invoke()
ElseIf (Not IsNull(ciparam) And Not IsNull(Params)) And ciparam.LastIndex = Params.LastIndex Then
Dim paramtypeinfo As Introspection.TypeInfo
Dim IsMatched As Boolean = False
For j As Integer = 0 To ciparam.LastIndex
paramtypeinfo = ciparam(j).ParameterType
If Not IsNull(paramtypeinfo) And paramtypeinfo.Name.Lowercase = ParamTypes(j).Lowercase Then
IsMatched = True
Else
IsMatched = False
Exit For j
End If
Next
If IsMatched Then
Return ci(i).Invoke(Params)
End If
End If
Next
End If
End If
End If
Return Nil
End Function