Dynamic Class Object Instantiation

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.

No. Class names cannot be string variables.

1 Like

I am aware of that.

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.

From Norman:

NO this DOESNT exist nor can you force introspection to do it

If you haven’t got a single declaration property parameter that IS the type you’re trying to create it will be completely stripped out

And even if you DO you still cant say
Find me this class’ typeinfo by name and create a new instance

Introspection only works with objects that already EXIST

3 Likes

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.

https://einhugur.com/Html/SerializationFramework/index.html

1 Like

have a look at ParamArray it would be useful for you.
do not use a constructor, use a extra method.

its more

Dim o as InterfaceObject = CreateObject("Contact") <- select case and each class share same interface
o.Set("name", "phone", "email")

also useful IsA

some flexibility you can get with Dictionary class

@Ed_Kleban

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

1 Like

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.

Included param type check

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

1 Like