Invoke member through reflection passing derived class as argument - vb.net

I'm trying to call the method 'MyMethod' of class 'CMyClass'. This method has a parameter of type "CBaseClass", and I'm passing an object of type "CDerivedClass".
Class CBaseClass
Public m_AMember As String
Sub MethodOne()
// DoSomething
End Sub
End Class
Class CDerivedClass
Inherits CBaseClass
Public m_MyMember As Integer
Sub MethodTwo()
// DoSomething
End Sub
End Class
Class CMyClass
Sub MyMethod(ByVal obj As CBaseClass)
// DoSomething
End Sub
End Class
Sub Main()
'load assembly
Dim objAssembly As Assembly = Assembly.LoadFrom("myfile.dll")
'create class instance and MethodInfo object
Dim t As Type = objAssembly.GetType("MyNamespace.CMyClass")
Dim obj As Object = Activator.CreateInstance(t)
Debug.Assert(obj IsNot Nothing)
Dim m As MethodInfo = t.GetMethod("MyMethod")
Debug.Assert(m IsNot Nothing)
'Init arguments (only one)
Dim par As New CDerivedClass()
Dim parameters As Object() = New Object(0) {par}
'invoke method
m.Invoke(obj, parameters) '<<<< ArgumentException here!
End Sub
The argument exception says "object of type 'MyNamespace.CDerivedClass' cannot be converted to type 'MyNamespace.CBaseClass'.
I changed "ByRef" to "ByVal" in MyMethod signature, but nothing changed.
I tried to change type of 'par' object with:
Dim par As CBaseClass = New CDerivedClass()
without success.
How I can invoke correctly the method "MyMethod" with an instance of derived class?
Thank you very much.

I just tried your code in a stand alone project and it worked fine. I only adjusted the assembly line to get the current assembly instead of from a file, and I changed the name in GetType to reflect my namespace. My guess is that perhaps your DLL you are loading is out of date, or you have duplicate definitions of the classes in your calling assembly, and thats why it cannot do the type conversion.
Imports System.Reflection
Module Module1
Sub Main()
'load assembly
Dim objAssembly As Assembly = Assembly.GetExecutingAssembly()
'create class instance and MethodInfo object
Dim t As Type = objAssembly.GetType("ConsoleApplication1.CMyClass")
Dim obj As Object = Activator.CreateInstance(t)
Debug.Assert(obj IsNot Nothing)
Dim m As MethodInfo = t.GetMethod("MyMethod")
Debug.Assert(m IsNot Nothing)
'Init arguments (only one)
Dim par As New CDerivedClass()
Dim parameters As Object() = New Object(0) {par}
'invoke method
m.Invoke(obj, parameters) '<<<< No ArgumentException here!
End Sub
End Module
Class CBaseClass
Public m_AMember As String
Sub MethodOne()
End Sub
End Class
Class CDerivedClass
Inherits CBaseClass
Public m_MyMember As Integer
Sub MethodTwo()
End Sub
End Class
Class CMyClass
Sub MyMethod(ByVal obj As CBaseClass)
End Sub
End Class

Finally I solved using serialization...
So 'par' is the string containing the serialized object of type CDerivedClass in the calling project.
MyMethod is changed to:
MyMethod(xml_CBaseClass As String)
In dll project the string parameter xml_CBaseClass is deserialized creating an object of CBaseClass.
Note: since I have derived type, Deserialization of derived class give another problem. The solution is https://stackoverflow.com/a/590711/1315873
(I just made a little change, using StringWriter for serialization, StringReader for deserialization, instead of using MemoryBuffer).
CBaseClass has fixed derived types so I wrote them hard-coded, but to be flexible you can do something like:
Dim subTypes as New List(Of Type) '<- all classes derived from 'myType'
For Each t In myType.Assembly.GetTypes()
If t.IsSubclassOf(myType) Then
subTypes.Add(t)
End If
Next
CBaseClass and all its derivated classes must have constructor New() without parameters.
I load assemblies using LoadFrom() since I don't know their names (I use Dir() to get all them from a known fixed folder).

Related

Assigning class Instance to Interface

Have two questions regarding the code below:
In the following code, the class Test implement the interface Test_Interafce. I have read in more than one articles that when a class implements an interface, we should assign the class instance to the interface. Going by that in the following code the statement Dim t As New Test() should be replaced by Dim t As Test_Interface = New Test(). However I do not understand the advantage or need for the same. When I instantiate the class Test by simply writing Dim t As New Test(), I am able to access all elements (even the procedures of the interface implemented by the class) through the instance "t" and the code seems to be working fine. So then why to assign a class instance to an interface?
Infact if I write Dim t As Test_Interface = New Test() then through "t" does not allow me to access the subroutine CHECK in the class Test. The error being displayed is: "CHECK is not a member of Test_Interface". So isnt that a disadvantage??!!
What is the use of the statement: Throw New NotImplementedException(). This statement comes automatically when I implement teh interface in a class/structure.
The code is found below:
Module Module1
Interface Test_Interface
Function Length(ByVal s As String) As Integer
Sub Details(ByVal age As Integer, ByVal name As String)
End Interface
Sub Main()
Dim t As New Test() 'Alternate definition: Dim t As Test_Interface = New Test()
t.Details(31, "Mounisha")
Console.WriteLine("Length of the string entered = {0}", t.Length("Hello"))
t.check()
Console.ReadLine()
End Sub
End Module
Class Test
Implements Test_Interface
Public Sub Details(age As Integer, name As String) Implements Test_Interface.Details
Console.WriteLine("Age of person = {0}", age)
Console.WriteLine("Name of person = {0}", name)
'Throw New NotImplementedException() ----> what does this do?
End Sub
Public Function Length(s As String) As Integer Implements Test_Interface.Length
Console.WriteLine("Original String: {0}", s)
Return s.Length()
'Throw New NotImplementedException()
End Function
Sub check()
Console.WriteLine("The sub can be accessed")
End Sub
End Class

Instantiate sub classes recursively without calling constructor?

Assume the following example class which mimics the type of class generated from an XSD file:
Public Class MyClass
Public Class MyInnerClass1
Public Class MyInnerInnerClass1
Public Property MyProp1 as string
Public Property MyProp2 as string
...
End Class
...
Public Property MyInnerInnerClassProp1 as MyInnerInnerClass1
End Class
Public property MyInnerClassProp1 as MyInnerClass1
Public property MyInnerClassProp2 as MyInnerClass2
...
End Class
Notice that there are no constructors. The level of inner classes, in this particular case, can go 5 levels deep, possibly circularly, before hitting a base property such as Property MyProp1 as string.
How can I recursively iterate through ALL of the public writable properties and initialize them as new instances of that object type without constructors?
For example, here is my current code which only goes one level deep at the moment?
Private Shared Sub InitProperties(obj As Object)
For Each prop As Object In obj.[GetType]().GetProperties(BindingFlags.[Public] Or BindingFlags.Instance).Where(Function(p) p.CanWrite)
Dim type__1 = prop.PropertyType
Dim constr = type__1.GetConstructor(Type.EmptyTypes)
'find paramless const
If type__1.IsClass Then
Dim propertyInstance = DirectCast(FormatterServices.GetUninitializedObject(type__1.GetType()), Object)
'Dim propInst = Activator.CreateInstance(type__1)
'prop.SetValue(obj, propInst, Nothing)
InitProperties(propertyInstance)
End If
Next
End Sub
I did some small edits to your code to get it to work on the example class you provided. (Although I did change the string properties to Integer to avoid one error.) I also added an argument for limiting the number of recursive calls, and a check that a property is equal to nothing before initializing it. (This check will only make a difference if you have circular references between static classes.)
Private Shared Sub InitProperties(obj As Object, Optional ByVal depth As Integer = 5)
For Each prop As PropertyInfo In obj.GetType().GetProperties(BindingFlags.Public Or BindingFlags.Instance).Where(Function(p) p.CanWrite)
Dim type__1 As Type = prop.PropertyType
If type__1.IsClass And IsNothing(prop.GetValue(obj, Nothing)) And depth > 0 Then
Dim propertyInstance As Object = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(type__1)
prop.SetValue(obj, propertyInstance, Nothing)
InitProperties(propertyInstance, depth - 1)
End If
Next
End Sub

Executing runtime code parameters

The following is some code to execute code at runtime:
Dim SourceCode As String = txtCode.Text
Dim Dlls() As String = {"System.dll", "System.Core.dll", "System.Data.dll", "System.Windows.Forms.dll"} 'Any referenced dll's
Dim Compiler As New VbCompiler(SourceCode, Dlls)
Dim CodeAssembly As Assembly = Compiler.Compile
If Compiler.Successful Then
Dim instance As Object = CodeAssembly.CreateInstance("TestCode.Class1")
Dim CodeType As Type = instance.GetType
Dim Info As MethodInfo = CodeType.GetMethod("ShowMessage")
Info.Invoke(instance, Nothing)
Else
For Each i As CompilerError In Compiler.Errors
MsgBox(i.ErrorText)
Next
End If
txtCode.text =:
Imports System.Windows.Forms
Namespace TestCode
Class Class1
Sub ShowMessage()
MessageBox.Show("Test")
End Sub
End Class
End Namespace
This works perfectly. I am wanting to know how to pass arguments to a function. ie
txtCode.text = :
Imports System.Windows.Forms
Namespace TestCode
Class Class1
Sub ShowMessage(ByVal x As String)
MessageBox.Show(x)
End Sub
End Class
End Namespace
I would like to run the 'ShowMessage" function with a string as the parameter, ("Test") for an example.
I am pretty sure it is at the following lines:
Dim Info As MethodInfo = CodeType.GetMethod("ShowMessage")
Info.Invoke(instance, Nothing)
But I cannot get it working.
You need to pass string value.
Info.Invoke(instance, New Object(){"Test"})
EDIT: Two arguments
Info.Invoke(instance, New Object(){"First","Second"})

VB.NET CType: How do I use CType to change an object variable "obj" to my custom class that I reference using a string variable like obj.GetType.Name?

The code below works for the class that I hard coded "XCCustomers" in my RetrieveIDandName method where I use CType. However, I would like to be able to pass in various classes and property names to get the integer and string LIST returned. For example, in my code below, I would like to also pass in "XCEmployees" to my RetrieveIDandName method. I feel so close... I was hoping someone knew how to use CType where I can pass in the class name as a string variable.
Note, all the other examples I have seen and tried fail because we are using Option Strict On which disallows late binding. That is why I need to use CType.
I also studied the "Activator.CreateInstance" code examples to try to get the class reference instance by string name but I was unable to get CType to work with that.
When I use obj.GetType.Name or obj.GetType.FullName in place of the "XCCustomers" in CType(obj, XCCustomers)(i)
I get the error "Type 'obj.GetType.Name' is not defined" or "Type 'obj.GetType.FullName' is not defined"
Thanks for your help.
Rick
'+++++++++++++++++++++++++++++++
Imports DataLaasXC.Business
Imports DataLaasXC.Utilities
Public Class ucCustomerList
'Here is the calling method:
Public Sub CallingSub()
Dim customerList As New XCCustomers()
Dim customerIdAndName As New List(Of XCCustomer) = RetrieveIDandName(customerList, "CustomerId", " CustomerName")
'This code below fails because I had to hard code “XCCustomer” in the “Dim item...” section of my RetrieveEmployeesIDandName method.
Dim employeeList As New XCEmployees()
Dim employeeIdAndName As New List(Of XCEmployee) = RetrieveIDandName(employeeList, "EmployeeId", " EmployeeName")
'doing stuff here...
End Sub
'Here is the method where I would like to use the class name string when I use CType:
Private Function RetrieveIDandName(ByVal obj As Object, ByVal idPropName As String, ByVal namePropName As String) As List(Of IntStringPair)
Dim selectedItems As List(Of IntStringPair) = New List(Of IntStringPair)
Dim fullyQualifiedClassName As String = obj.GetType.FullName
Dim count As Integer = CInt(obj.GetType().GetProperty("Count").GetValue(obj, Nothing))
If (count > 0) Then
For i As Integer = 0 To count - 1
'Rather than hard coding “XCCustomer” below, I want to use something like “obj.GetType.Name”???
Dim Item As IntStringPair = New IntStringPair(CInt(CType(obj, XCCustomers)(i).GetType().GetProperty("CustomerId").GetValue(CType(obj, XCCustomers)(i), Nothing)), _
CStr(CType(obj, XCCustomers)(i).GetType().GetProperty("CustomerName").GetValue(CType(obj, XCCustomers)(i), Nothing)))
selectedItems.Add(Item)
Next
End If
Return selectedItems
End Function
End Class
'+++++++++++++++++++++++++++++++
' Below are the supporting classes if you need to see what else is happening:
Namespace DataLaasXC.Utilities
Public Class IntStringPair
Public Sub New(ByVal _Key As Integer, ByVal _Value As String)
Value = _Value
Key = _Key
End Sub
Public Property Value As String
Public Property Key As Integer
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCCustomer
Public Property CustomerId As Integer
Public Property CustomerName As String
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCCustomers
Inherits List(Of XCCustomer)
Public Sub New()
PopulateCustomersFromDatabase()
End Sub
Public Sub New(ByVal GetEmpty As Boolean)
End Sub
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCEmployee
Public Property EmployeeId As Integer
Public Property EmployeeName As String
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCEmployees
Inherits List(Of XCEmployee)
Public Sub New()
PopulateEmployeesFromDatabase()
End Sub
Public Sub New(ByVal GetEmpty As Boolean)
End Sub
End Class
End Namespace
From MSDN
CType(expression, typename)
. . .
typename : Any expression that is legal
within an As clause in a Dim
statement, that is, the name of any
data type, object, structure, class,
or interface.
This is basically saying you can't use CType dynamically, just statically. i.e. At the point where the code is compiled the compiler needs to know what typename is going to be.
You can't change this at runtime.
Hope this helps.
Since List(Of T) implements the non-generic IList interface, you could change your function declaration to:
Private Function RetrieveIDandName(ByVal obj As System.Collections.IList, ByVal idPropName As String, ByVal namePropName As String) As List(Of IntStringPair)
And then your troublesome line would become (with also using the property name parameters):
Dim Item As IntStringPair = New IntStringPair(CInt(obj(i).GetType().GetProperty(idPropName).GetValue(obj(i), Nothing)), _
CStr(obj(i).GetType().GetProperty(namePropName).GetValue(obj(i), Nothing)))
Of course, you could still have the first parameter by Object, and then attempt to cast to IList, but that's up to you.
ctype is used to convert in object type.

.net dynamic loading

I've seen some other responses about this and they talk about interfaces but I'm pretty sure you can do this with classes and base classes but I can't this to work.
Public Class Behavior
Private _name As String
Public ReadOnly Property Name As String
Get
Return _name
End Get
End Property
Public Property EditorUpdate As Boolean
Public Sub New(ByVal name As String)
_name = name
EditorUpdate = False
End Sub
Public Overridable Sub Update()
End Sub
' runs right away in editor mode. also runs when in stand alone game mode right away
Public Overridable Sub Start()
End Sub
' runs after game mode is done and right before back in editor mode
Public Overridable Sub Finish()
End Sub
' runs right when put into game mode
Public Overridable Sub Initialize()
End Sub
' runs when the game is complete in stand alone mode to clean up
Public Overridable Sub Destroy()
End Sub
End Class
Public Class CharacterController
Inherits Behavior.Behavior
Public Sub New()
MyBase.New("Character Controller")
End Sub
Public Overrides Sub Update()
' TODO: call UpdateController()
' THINK: how can UpdateController() get the controller entity it's attached to?
' Behaviors need a way to get the entity they are attached to. Have that set when it's assigned in the ctor?
End Sub
End Class
Dim plugins() As String
Dim asm As Assembly
plugins = Directory.GetFileSystemEntries(Path.Combine(Application.StartupPath, "Plugins"), "*.dll")
For i As Integer = 0 To plugins.Length - 1
asm = Assembly.LoadFrom(plugins(i))
For Each t As Type In asm.GetTypes
If t.IsPublic Then
If t.BaseType.Name = "Behavior" Then
behaviorTypes.Add(t.Name, t)
Dim b As Behavior.Behavior
b = CType(Activator.CreateInstance(t), Behavior.Behavior)
'Dim o As Object = Activator.CreateInstance(t)
End If
End If
Next
Next
When it tries to convert whatever Activator.CreateInstance(t) returns to the base class of type Behavior I'm getting invalid cast exception. That type should be of CharacterController which is defined as a child of Behavior so why wouldn't it let me cast that? I've done something like this before but I can't find my code. What am I missing?
This may not be an answer to your question (it also might resolve your exception -- who knows), but it is something that needs to be pointed out. These lines:
If t.IsPublic Then
If t.BaseType.Name = "Behavior" Then
Should really be changed to one conditional like this one:
If t.IsPublic AndAlso (Not t.IsAbstract) AndAlso _
GetType(Behavior.Behavior).IsAssignableFrom(t) Then
Otherwise, if somebody defines a random type called "Behavior" in their own assembly and derives it from another type, your code will think it is a plugin. Additionally, if someone derives your Behavior type and then derives that type (two levels of inheritance) this code will incorrectly skip over that type. Using the IsAssignableFrom method is a quick and easy way to ensure that one type does actually derive from the specific type you want (instead of any type that shares the same name), even if there is another type in between your types in the inheritance tree. The additional check against t.IsAbstract will also ensure that you don't try to instantiate an abstract subtype of your base plugin type.
This works for me:
Dim ctor As Reflection.ConstructorInfo = _
t.GetConstructor(New System.Type() {})
Dim o As Object = ctor.Invoke(New Object() {})
Dim plugin As Plugin = TryCast(o, Plugin)
(If I find t, I invoke the parameterless constructor.)
[I just realized this is probably what Activator.CreateInstance does, so I replaced my code with yours and it worked your way -- so this probably won't help you]