1

I am currently working with a file that has data inside.

The data can be in various classes and those classes are defined in the same file.

I am currently able to output the classes to json and also I am able to cast the data as json to those classes

The problem is I would like to be able to create a class dynamically to handle the data on the go not simply print it out as file.

Example of class:

Class id:4 - name Item

public Int id
public I18N nameId
public Int typeId
public I18N descriptionId
public Int iconId
public Int level
public Int realWeight
public Bool cursed
public Int useAnimationId
public Bool usable
public Bool targetable
public Bool exchangeable
public Double price
public Bool twoHanded
public Bool etheral
public Int itemSetId
public String criteria
public String criteriaTarget
public Bool hideEffects
public Bool enhanceable
public Bool nonUsableOnAnother
public Int appearanceId
public Bool secretRecipe
public Int recipeSlots
public vector<UInt> recipeIds
public vector<UInt> dropMonsterIds
public Bool bonusIsSecret
public vector<EffectInstance> possibleEffects
public vector<UInt> favoriteSubAreas
public Int favoriteSubAreasBonus
public Int craftXpRatio
public Bool needUseConfirm
public Bool isDestructible
public vector<vector<Double>> nuggetsBySubarea
public vector<UInt> containerIds
public vector<vector<Int>> resourcesBySubarea

The types are defined from a Uint with an enum:

  Public Enum GameDataTypeEnum
        Int = -1
        Bool = -2
        [String] = -3
        [Double] = -4
        I18N = -5
        UInt = -6
        Vector = -99
    End Enum

To solve this I tried to translate the enum type to the vb.net type for instance:

Dim myType1 As Type = Type.GetType("System.Int32")

I then tried using the Activator.CreateInstance like this:

Dim Type1 As Type = Type.GetType("System.String")
Dim Type2 As Type = Type.GetType("System.Int32")
Dim dictType As Type = GetType(Dictionary(Of , )).MakeGenericType(Type1, Type2)
Dim dict = Activator.CreateInstance(dictType)
dict.add("test", 32)
Console.WriteLine(dict.Item("test"))

This works fine however looking back at this solution it is not viable as the classes have different types. Would you see anyways to create the classes from the file and dynamically generate them?

Worst case scenario I could generate all the classes manually but there is a lot of classes and would take a lot of time


Vincent's solution works fine apart for a minor problem.

When I read a file there is the possibility that a field can conatain another field those are called vectors which can be represented by: List Of (Contained Value)

For now I Simply do this:

For Each field As GameDataField In classDefinition.Value.Fields
    Console.WriteLine(field.fieldName & " --- " & testcast(getFieldTypeString(field)))
    Console.WriteLine(field.fieldName & " --- " & field.fieldType)
    ClassProperties.Add(field.fieldName, Type.GetType(testcast(getFieldTypeString(field))))
Next

My testcast() function simply returns a string converting the enum to correct format like Int to System.Int32

And for my getFieldTypeString I have:

If isPrimitiveFieldType(field) Then
    Return getPrimitiveFieldTypeString(field)
Else
    Return getCompositeFieldTypeString(field)
End If

Primitive gives:

Private Function getPrimitiveFieldTypeString(field As GameDataField) As String
    Return If(field.fieldType > 0, classDefinitions(CInt(field.fieldType)).Name, field.fieldType.ToString())
End Function

Composite gives:

Private Function getCompositeFieldTypeString(field As GameDataField) As String
    Dim compositeFieldTypeBuilder As New StringBuilder()
    compositeFieldTypeBuilder.Append("System.Collections.Generic.List(Of ")
    If getFieldTypeString(field.innerField) = "UInt" Then
        compositeFieldTypeBuilder.Append("UInt32)")
    ElseIf getFieldTypeString(field.innerField) = "Int" Then
        compositeFieldTypeBuilder.Append("Integer)")
    Else
        compositeFieldTypeBuilder.Append(getFieldTypeString(field.innerField)).Append(")")
    End If
    Return compositeFieldTypeBuilder.ToString()
End Function

How could I add a property to the ClassProperties to represent:

List of a List of a Type
Mederic
  • 1,949
  • 4
  • 19
  • 36
  • Possible duplicate of [How to create a Dictionary with any types in Type variables?](https://stackoverflow.com/questions/11717264/how-to-create-a-dictionary-with-any-types-in-type-variables) – Visual Vincent Aug 17 '17 at 12:56
  • @VisualVincent thinking back to it I don't think the the dictionnary approach would be good idea since in the classes theres different types. so like in the sample class I couldn't put all the data. Would it be possible to have a similar way to do it but with classes? – Mederic Aug 19 '17 at 07:50
  • This looks rather promising: https://stackoverflow.com/a/26971289/3740093 – Visual Vincent Aug 19 '17 at 09:12
  • Also if you use that code, make the change suggested in this answer: https://stackoverflow.com/a/27499618/3740093 – Visual Vincent Aug 19 '17 at 09:19
  • @VisualVincent I tried the solution you pointed to me so I feed my dictionnary with the properties I would like to be able to access for instance dic.add("id",Type1) where type1 = "system.int32 but then I was wondering how do I access those members cause I can't do MyGenClass.id – Mederic Aug 20 '17 at 18:18
  • https://stackoverflow.com/a/43874589/3740093 – Visual Vincent Aug 21 '17 at 07:33
  • when I use: `dim obj(1) as object` `obj(0) = 32` `MyGenClass.GetProperty("set_id", obj)` then I get the error: **{"Object reference not set to an instance of an object."}** – Mederic Aug 21 '17 at 15:01
  • 1
    If you want to _set_ the property then my _**`GetProperty`**_ method won't work. I'll write a more complete answer in a while. -- Also, keep in mind that `Dim obj(1) As Object` creates an array of _**two**_ elements since you declare its _**upper bound index**_. To declare it with one item either do `Dim obj(0) ...` or the way I prefer it: `Dim obj(1 - 1) ...`. – Visual Vincent Aug 21 '17 at 15:23
  • Thanks a lot for the help, I want to be able to set all the dynamic fields but also get them. And yeah thanks for the reminder on the array. – Mederic Aug 21 '17 at 15:39
  • Hi again! It's been a couple of days, I was wondering if my answer is what you needed or if I am missing something? – Visual Vincent Aug 25 '17 at 08:25
  • @VisualVincent Hey unfortunatly im on small holiday right now so didn't have the chance to try your solution i'll be back in few days and will definetly try it then . – Mederic Aug 26 '17 at 09:38
  • That's okay, was just curious to why I suddenly didn't hear anything from you when I had before. No pressure! Enjoy your holiday! ;) – Visual Vincent Aug 26 '17 at 10:56

1 Answers1

1

As already mentioned in the comments this answer (together with this fix) will help you create classes dynamically.

To get/set the properties of those classes we can create two extension methods that uses reflection in order to access the property getters/setters by name:

Imports System.Runtime.CompilerServices

Public Module Extensions

    ''' <summary>
    ''' Uses Reflection to get the value of the specified property.
    ''' </summary>
    ''' <param name="Instance">The object instance which's property to access.</param>
    ''' <param name="PropertyName">The name of the property which's value to get.</param>
    ''' <param name="Arguments">Arguments to pass along to the property method (if any).</param>
    ''' <remarks></remarks>
    <Extension()> _
    Public Function GetProperty(ByVal Instance As Object, ByVal PropertyName As String, Optional ByVal Arguments As Object() = Nothing) As Object
        Return Instance.GetType().GetProperty(PropertyName).GetValue(Instance, Arguments)
    End Function

    ''' <summary>
    ''' Uses Reflection to set the value of the specified property.
    ''' </summary>
    ''' <param name="Instance">The object instance which's property to access.</param>
    ''' <param name="PropertyName">The name of the property which's value to set.</param>
    ''' <param name="Value">The new value to give the property.</param>
    ''' <param name="Arguments">Arguments to pass along to the property method (if any).</param>
    ''' <remarks></remarks>
    <Extension()> _
    Public Sub SetProperty(ByVal Instance As Object, ByVal PropertyName As String, ByVal Value As Object, Optional ByVal Arguments As Object() = Nothing)
        Instance.GetType().GetProperty(PropertyName).SetValue(Instance, Value, Arguments)
    End Sub
End Module

NOTE:

Since it seems you cannot use extension methods on a System.Object you'll just have to call them normally:

Extensions.SetProperty(MyObject, "MyProperty", MyValue)

However on anything that isn't directly a System.Object you can use these extension methods like you normally would:

Button1.SetProperty("Text", "Click me!")

Now you can use them like this:

'Just a quick mockup of properties.
Dim ClassProperties As New Dictionary(Of String, Type) From {
    {"name", Type.GetType("System.String")},
    {"id", Type.GetType("System.Int32")},
    {"hasCar", Type.GetType("System.Boolean")}
}

'Create the class type.
Dim CustomClass As Type = CreateClass("Person", ClassProperties)

'Create an instance of the class.
Dim MyPerson As Object = Activator.CreateInstance(CustomClass)

'Modify its properties.
Extensions.SetProperty(MyPerson, "name", "John Doe")
Extensions.SetProperty(MyPerson, "id", 12345)
Extensions.SetProperty(MyPerson, "hasCar", True)

MessageBox.Show( _
        String.Join(Environment.NewLine, {Extensions.GetProperty(MyPerson, "name"), _
                                          Extensions.GetProperty(MyPerson, "id"), _
                                          Extensions.GetProperty(MyPerson, "hasCar")}), _
        "", MessageBoxButtons.OK, MessageBoxIcon.Information)

Profit!

Result

Community
  • 1
  • 1
Visual Vincent
  • 18,045
  • 5
  • 28
  • 75
  • This works really fine! thanks a lot however a little question when I dynamically create my Class Properties dic in the file it sometimes read: `List (Of List Of (UInt))` This would translate as: `System.Collections.Generic.List(Of System.Collections.Generic.List(Of UInt)` or would it : `System.Collections.Generic.List(Of List Of(UInt)` – Mederic Aug 30 '17 at 09:05
  • @Mederic : `List(Of List Of (UInt))` isn't valid syntax. Where do you get that part from? Do you have a sample? – Visual Vincent Aug 30 '17 at 09:07
  • in my file read I have a list of a list of a integer for sub areas. In the file I can have what I called a Primitive or Composite field as some fields have the type as (-99) which represents in ActionScript a Vector which translates to a List (Of T) the problem is how would I define a property which is a list of a list of a type. If you dont understandI can edit my question for easier explanation – Mederic Aug 30 '17 at 09:17
  • I edited the bottom of my question to try explain better – Mederic Aug 30 '17 at 09:36
  • @Mederic : Hmm... From your description I would say that your first suggestion: `System.Collections.Generic.List(Of System.Collections.Generi‌​c.List(Of UInteger))` is what you need (nested lists). – Visual Vincent Aug 30 '17 at 11:46
  • But when I use that then it tells me cant input empty value. As if it doesnt recognise: GetType("System.Collections.Generic.List(Of System.Collections.Generi‌​‌​c.List(Of UInteger‌​))") – Mederic Aug 30 '17 at 13:54
  • @Mederic : Oh, that's because with `Type.GetType()` you gotta write it like this: `System.Collections.Generic.List\`1[System.Collections.Generic.List\`1[System.UInteger]]`. – Visual Vincent Aug 30 '17 at 13:56
  • @Mederic : The `\`1` part specifies how many generic parameters the class has. So (for instance) for a `Dictionary(Of TKey, TValue)` you'd write `\`2`. – Visual Vincent Aug 30 '17 at 13:58
  • So it would be: `Type.GetType("System.Collections.Generic.List`1[System.Collections.Generic‌​.List`1[System.UInte‌​ger]]"` ? – Mederic Aug 30 '17 at 13:59
  • 1
    @Mederic : No wait, it should be `System.UInt32` instead of `System.UInteger` (the rest was correct). My mistake, sorry. :# – Visual Vincent Aug 30 '17 at 14:01
  • Ok one more question to be even more annoying. I realised that in one property I have a List (Of AnotherClass) so the first class generated is then called in a list how would I call that class by it's name in the Type: Type.GetType("System.Collections.Generic.List`1[EffectInstance]") – Mederic Aug 31 '17 at 17:05
  • @Mederic : Don't worry, you're not annoying. I'm glad to help! ;) -- The best is if you still have access to the custom class's type. This is because you must specify a full name (or possibly even a fully qualified name) if you want to refer to it dynamically. Try whatever works: `Type.GetType("System.Collections.Generic.List\`1[" & CustomClassType.FullName & ]")` or: `Type.GetType("System.Collections.Generic.List\`1[" & CustomClassType.AssemblyQualifiedName & ]")`. – Visual Vincent Aug 31 '17 at 23:39
  • I tried both the .FullName and the AssemblyQualifiedName and both don't work. – Mederic Sep 01 '17 at 10:51
  • @Mederic : Ah, I just saw this on the [**documentation**](https://msdn.microsoft.com/en-us/library/w3f99sx1.aspx): _"Type.GetType only works on assemblies loaded from disk [...]"_ – Visual Vincent Sep 01 '17 at 11:03
  • So would you see any way around it :/ – Mederic Sep 01 '17 at 11:06
  • @Mederic : You'll have to add an extra condition to check if you need a list of a custom class, in which case you have to use code similar to this instead: https://stackoverflow.com/a/4661237 (C#, can be converted to VB.NET) – Visual Vincent Sep 01 '17 at 11:07
  • But then how would I integrate the IList into the class? do I just do: `ClassProperties.Add(field.fieldName, MyIList)`? – Mederic Sep 01 '17 at 11:10
  • @Mederic : Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/153457/discussion-between-visual-vincent-and-mederic). – Visual Vincent Sep 01 '17 at 11:11