MattCucco
MattCucco

Reputation: 133

How to use GetType and GetFields?

I'm updating a program and there is about 40 classes. I need to create a function that takes two lists of type object as parameters. Both lists only have one item in them (A version of the item BEFORE any changes were made, and AFTER the changes happened). I'm using these objects to create a single object to implement an UNDO button. With these parameters I need to get the type of each and make sure they match, if not then something went wrong. Next I'll need to read in the fields/properties/members (Not sure what to choose) and then compare them to each other and find what changed so I can set that as the item description. I don't want to trace the whole code and add specific functions for each and I know there has got to be a way to do this generically. I have created this small mock-up program that semi works for what I am trying to do. I can get the class type out of the object in list, but i have no idea how to get fields or whatever.

I'm using a large database with entity framework. Also Using VB.NET!
Thanks for the help! Here's code for generic program:

Imports System.Reflection

Module Module1

Sub Main()


    Dim Myself As New Human("Matthew", "Cucco", Now, "Blonde", 19, False)
    Dim NotMe As New Human("Jake", "Cucco", Now, "Blonde", 19, False)
    Dim Him As New Employee("Matt", "Cucco", Now, "Blonde", 19, False, 215, "LuK", True)
    Dim Her As New Customer("Jessie", "Keller", Now, "Blonde", 19, True, 25, "Cereal", "[email protected]")

    Dim ListofPeople As IList(Of Object) = {Myself, NotMe, Him, Her}
    Dim ListofPeople2 As IList(Of Object) = {Myself, NotMe, Him, Her}


    ObjectsAreSameClass(ListofPeople, ListofPeople2)
    Console.ReadKey()


End Sub

Private Function ObjectsAreSameClass(object1 As IList(Of Object), object2 As IList(Of Object)) As Boolean

    Dim ObjectType As Type = object1.First.GetType()
    Dim AreSameClass As Boolean = Nothing
    Console.WriteLine(ObjectType.ToString)

    If (object1.First.GetType() = object2.First.GetType()) Then
        AreSameClass = True
        Console.WriteLine("Object1 is of type: " + object1.First.GetType().Name)
        Console.WriteLine("Object2 is of type: " + object2.First.GetType().Name)
        If (object1.First.GetType().Name = "Human") Then
            Console.WriteLine("Yep this works")
        End If


    Else
        AreSameClass = False
        Console.WriteLine("Object1 is of type: " + object1.First.GetType().Name)
        Console.WriteLine("Object2 is of type: " + object2.First.GetType().Name)
        If (object1.First.GetType().Name = "Human") Then
            Console.WriteLine("Yep this works")
            Console.WriteLine(object1.First.GetType().GetFields().ToString)
        End If

    End If

    Dim MyField As PropertyInfo() = ObjectType.GetProperties()
    Dim i As Integer
    For i = 0 To MyField.Length - 1
        Console.WriteLine(MyField(i).ToString)
    Next i

    Console.WriteLine("Objects are equal? t/f : " + AreSameClass.ToString)

    Return AreSameClass
End Function


Public Class Human

    Public FirstName As String
    Public LastName As String
    Public Birthdate As Date
    Public HairColor As String
    Public Age As Integer
    Public Gender As Boolean   'False for male, true for female




    Public Sub New()

        FirstName = ""
        LastName = ""
        Birthdate = Now
        HairColor = ""
        Age = 0
        Gender = False

    End Sub

    Public Sub New(f As String, l As String, b As Date, h As String, a As Integer, g As Boolean)

        FirstName = f
        LastName = l
        Birthdate = b
        HairColor = h
        Age = a
        Gender = g

    End Sub

End Class


Public Class Employee
    Inherits Human

    Dim EmployeeId As Integer
    Dim PlaceOfEmployment As String
    Dim IsManager As Boolean

    Public Sub New()
        MyBase.New()
        EmployeeId = 0
        PlaceOfEmployment = ""
        IsManager = False
    End Sub

    Public Sub New(f As String, l As String, b As Date, h As String, a As Integer, g As Boolean, i As Integer, p As String, m As Boolean)
        MyBase.New(f, l, b, h, a, g)
        EmployeeId = i
        PlaceOfEmployment = p
        IsManager = m
    End Sub


End Class




Public Class Customer
    Inherits Human
    'used for testing

    Dim IdNumber As Integer
    Dim FavoriteItem As String
    Dim email As String
    Public Sub New()
        MyBase.New()
        IdNumber = 0
        FavoriteItem = ""
        email = ""
    End Sub

    Public Sub New(f As String, l As String, b As Date, h As String, a As Integer, g As Boolean, i As Integer, fav As String, e As String)
        MyBase.New(f, l, b, h, a, g)
        IdNumber = i
        FavoriteItem = fav
        email = e
    End Sub


End Class

End Module

This Currently displays this:

TestProject.Module1+Human
Object1 is of type: Human
Object2 is of type: Human
Yep this works
Objects are equal? t/f : True

Also for reference, here is my main program that I will be implementing this into:

Function NewItem(Before As IEnumerable(Of Object), After As IEnumerable(Of Object), ObjectType As String)

    ObjectsAreSameClass(Before, After, ObjectType)  'Check if objects are same class

    Dim BeforeFields() As FieldInfo = GetFieldData(Before)  'gets all field info, saves to an array
    Dim AfterFields() As FieldInfo = GetFieldData(After)

    'Now check and make sure the objects are not the same
    Dim ThisChanged As FieldInfo
    If (ObjectValuesAreEqual(BeforeFields, AfterFields) = True) Then
        'These objects did not not change
        ThisChanged = Nothing
    Else
        'Change occured, find out where
        ThisChanged = FindWhatChanged(BeforeFields, AfterFields)
    End If


    'Create a new UndoRedo item and give it these values
    Dim UndoRedoNow As New ClsUndoRedo
    UndoRedoNow.BeforeObject = Before.Single
    UndoRedoNow.AfterObject = After.Single
    UndoRedoNow.ObjectCounter += 1
    UndoRedoNow.WhatChanged = ThisChanged

    If WhatGroupChanged.isDeleted Then
        UndoRedoNow.WhatAction = Before.Single.GetType().ToString + " item was Deleted"
    ElseIf WhatGroupChanged.isNew Then
        UndoRedoNow.WhatAction = After.Single.GetType().ToString + " item was created"
    ElseIf WhatGroupChanged.isChanged Then
        UndoRedoNow.WhatAction = After.Single.GetType().ToString + " item was changed"
    End If
    UndoRedoNow.WhatGroupChanged.isRedo = False 'Make sure it is not a redo object

    'Now add object to list
    ChangeLog.Add(UndoRedoNow)

    Return Nothing
End Function


Private Function ObjectsAreSameClass(before As IEnumerable(Of Object), after As IEnumerable(Of Object), WhatType As String) As Boolean

    Dim AreSameClass As Boolean = False

    Try
        If (before.Single.GetType() = after.Single.GetType() Or (before Is Nothing) Or (after Is Nothing)) Then
            'Objects are of the same class or nothing
            If before Is Nothing Then
                WhatGroupChanged.isNew = True  'New item
            ElseIf after Is Nothing Then
                WhatGroupChanged.isDeleted = True  'Deleted item
            Else
                WhatGroupChanged.isChanged = True  'item was changed
            End If
            AreSameClass = True

        End If

    Catch
        'Need to raise error

    End Try
    Return AreSameClass
End Function

''' <summary>
''' This function will return all of the fields for a certain class as well as the data stored in them
''' </summary>
''' <param name="list"></param>
''' <returns></returns>
Public Shared Function GetFieldData(ByVal list As IList(Of Object)) As FieldInfo()

    Dim fields() As FieldInfo = list.Single.GetType().GetFields()

    Return fields

End Function

''' <summary>
''' This function will check that the values in the datafields are not equal
''' </summary>
''' <param name="Before"></param>
''' <param name="After"></param>
''' <returns></returns>
Private Function ObjectValuesAreEqual(Before() As FieldInfo, After() As FieldInfo) As Boolean

    Dim isEqual As Boolean = New Boolean    'This will keep track of if the elements are equal or not

    For index As Integer = 0 To (Before.Count - 1)
        If Before.ElementAt(index).GetValue(Before.ElementAt(index)).Equals(After.ElementAt(index).GetValue(After.ElementAt(index))) Then
            'They are equal so set to true
            isEqual = True

        Else
            'They are not equal so set to false and return
            isEqual = False
            Return isEqual

        End If
    Next
    Return isEqual
End Function

Private Function FindWhatChanged(Before() As FieldInfo, After() As FieldInfo) As FieldInfo
    Dim ThisIsChange As FieldInfo
    For index As Integer = 0 To (Before.Count - 1)
        If Before.ElementAt(index).GetValue(Before.ElementAt(index)).Equals(After.ElementAt(index).GetValue(After.ElementAt(index))) Then
            ThisIsChange = After.ElementAt(index)
            Return ThisIsChange

        Else
            'Raise error
        End If
    Next

End Function

Upvotes: 1

Views: 2813

Answers (1)

piaste
piaste

Reputation: 2058

The proper way to preserve type information when working with unknown types is to write a generic function (and if necessary generic classes, structures, etc.).

Using GetType, in a perfect world, should never be needed.

Generic functions look like this:

Public Function MyGenericFunction(Of T)(myArg as T) as Integer
    ' do something with myArg1, myArg2 ... without knowing their exact type
    Return 0
End Function

' or with multiple types
Public Function MyGenericFunction2(Of T1, T2, ... )(myArg1 as T1, myArg2 as T2, ...) as T1()
    ' do something with myArg1, myArg2 ... without knowing their exact type
    Return { myArg1 }
End Function

When you call these functions, the generic types are usually automatically deduced from the arguments you passed. If they can't be guessed, you will need to explicitly annotate the types, like this:

Dim x = MyGenericFunction(Of SomeClass1)(foo)
Dim x = MyGenericFunction(Of SomeClass2)(foo)

A full guide here: https://msdn.microsoft.com/en-us/library/w256ka79.aspx


However, if you need to handle specific types with the same function, then what you want to do use is a more narrow tool: overloading, or more technically parametric polymorphism.

What that means is, you will need to provide two (or more) different definitions of the same function ( = having the same name), that accept parameters of different types.

A simple example:

Public Class MyClass1
    Public Foo1 As String  = "foo1"
End Class

Public Class MyClass2
    Public Foo2 As String = "foo2"
End Class

Public Sub MyFunction(arg as MyClass1)
    Console.WriteLine(arg.Foo1)
End Sub

Public Sub MyFunction(arg as MyClass2)
    Console.WriteLine(arg.Foo2)
End Sub

Dim x as Object

' let's give x a random value of either MyClass1 or MyClass2,
' and we don't know in advance which one
If DateTime.Today.DayOfWeek = DayOfWeek.Tuesday Then 
  x = new MyClass1
Else
  x = new MyClass2
End If

' the program will automatically invoke the correct function based on x's value, and print either "foo1" or "foo2"
MyFunction(x)

Upvotes: 1

Related Questions