Invoke method dynamically in VB.Net - vb.net

I've some classes defined in a dll file. These are in the form of com api.
I'm trying to create an object of one of the class dynamically and than setting some property of that object.
When I set the property manually, it works, but when I try to invoke the same using reflection it gives the error that
Object does not match target type.
Following is my code
Private Sub SetObjectValue(ByVal SelectedObject As SAPbobsCOM.BoObjectTypes, ByVal ClassName As String, ByVal FieldName As String, ByVal SetValue As String, ByVal KeyValue As String)
Dim oObject As Object
Dim myAssembly As Reflection.Assembly = Reflection.Assembly.LoadFrom("interop.sapbobscom.dll")
Dim myType As Type = myAssembly.GetType(ClassName)
Dim myMember() As MemberInfo = myType.GetMember(FieldName)
Dim myProperty As PropertyInfo = CType(myMember(0), PropertyInfo)
Dim myMethod As MethodInfo = myProperty.GetSetMethod
oObject = oCompany.GetBusinessObject(SelectedObject)
oObject.GetByKey(KeyValue)
myProperty.SetValue(oObject, CDbl(SetValue), Nothing)
End Sub
It gives the error when SetValue method is called. Instead, if I use this like following it works fine:
oObject.CreditLimit = 129
oObject.Update
Where CreditLimit is a property of the given class, and update is a method which I have to call, after the value is set, so that the value in underlying database is updated.
Similarly GetByKey is used to retrieve the value of the object from the underlying database, where the value of the primary key field has to be passed.
Since there are multiple classes and each class has lots of different properties, therefore calling them dynamically will help a lot.
Thanks
Rahul Jain
Just tried doing what casper has suggested here. It gives an error saying - Member not found. (Exception from HRESULT: 0x80020003 (DISP_E_MEMBERNOTFOUND))
Rahul
Its done. Instead of vbSet, I used vbLet and it completed successfully.
Thanks
Rahul

I am kind of curious why you are doing this, as VB will do all of it for you. You simply have to declare as type object and then make the call, or are you using an option (I believe it is strict?) which prevents you from letting the compiler emit the reflection code for late-bound calls?
If you need to take a parameter, you should be able to use CallByName as well:
Private Sub SetObjectValue(ByVal SelectedObject As SAPbobsCOM.BoObjectTypes, ByVal ClassName As String, ByVal FieldName As String, ByVal SetValue As String, ByVal KeyValue As String)
Dim oObject As Object
oObject = oCompany.GetBusinessObject(SelectedObject)
oObject.GetByKey(KeyValue)
CallByName(oObject, FieldName, vbSet, CDbl(SetValue))
End Sub

Related

VB.NET Variable Passed ByRef to Constructor Available to Other Class Methods

I'm working on a class to parse templates. Here's an abbreviated version of what I have:
Public Class Parser
Public Vars As New Dictionary(Of String, String)
Public Tags As New Dictionary(Of String, String)
Private Template As String
Public Sub New(ByRef _Template As String, Optional ByVal _Recursing As Boolean = False)
Recursing = _Recursing
Template = _Template
End Sub
Public Sub Go()
' Do stuff, extract a portion of the template
Dim Page as Parser = New Parser(portion, True)
Page.Go()
End Sub
End Class
As you can see, it's designed to be recursive. When the class is first instantiated, the caller passes a template name to the constructor, then sets various stuff and calls Go(). The problem comes when it recurses: Go() creates a new instance, and instead of the template name, passes a portion of the template to be worked on, which is why _Template is ByRef. The problem is, the new instance of Go() does not have access to it. VB doesn't have pointers, so is there any way to make that reference available to the rest of the class from the constructor? I've googled around and read a number of articles here, but can't find anything that answers my question. This article makes it sound like the Object data type will do what I want, so I tried
Private Template As Object
but it doesn't do what I want. I also found this which seems to be asking the same question, and seems to say that changing the parm to ByVal does the trick, but that makes no sense.

Optional form's name parameter passed to a sub

Hello. I have a small problem and I can't figure it out: how can I create an optional parameter for a form's name? For example I want to do something like this:
Private Sub Draw(ByVal Start_Pos As Point, ByVal End_Pos As Point, Optional ByVal Form_Name As Form = Cube)
End Sub
I'm not sure if what I want is possible.I just know that the code is not correct because I must specify to the program that "Cube" is a form not just a string...
A constant value is required for Optional params.
When a constant value is not possible to establish at design time for whatever reason, then the easiest trick is to set the vaue as Nothing and check if the value is nothing inside the block, if it is, then set their default value at execution time.
An example:
Private Sub Draw(ByVal startPos As Point, ByVal endPos As Point,
Optional ByVal form As Form = Nothing)
If (form Is Nothing) Then
form = Cube
End If
' ...
End Sub
An adaptation to the real problem that you'd described:
Private Sub Draw(ByVal startPos As Point, ByVal endPos As Point,
Optional ByVal formName As String = "")
If ( String.IsNullOrEmpty(formName) ) Then
formName = Cube.Name
End If
' ...
End Sub
As mentioned in documents for Optional Parameters for each optional parameter, you must specify a constant expression as the default value of that parameter.
So you can't use a form instance as default value for optional parameter.
If you need to set a form name as default value for your string optional parameter:
You can set one of your application forms full name (including namespace) as default value and then create an instance of that form using Activator.CreateInstance and Unwrap the object and use DirectCast to cast it to form and show it later.
To create other forms that their names passed as that parameter, you can use Activator.CreateInstance also you can have a Dictionary(Of String, Type) containing names and form types or Dictionary(Of String, Form) containing names and form instances and use this dictionary to get the instance of form.

Interface does not behave like an Object?

I have a little problem with an interface. A bunch of my classes implement the ILayoutObject interface. A method declares a variable as ILayoutObject (defaulting it as Nothing) and then runs some code which decides which object it should be. The problem is, the evaluation code runs in a method which receives the variable as a parameter and assigns an object to it. With objects, this would be no problem. The object would be affected by the changes in the method and everything would be OK. Howeverm, when using an interface, the variable in the calling code remains Nothing and behaves like a normal variable. Does anyone have any ideas on how to circumvent that? Alas, due to code structure I am unable to use ByRef or functions :(
Here is some code:
Protected LayoutHandler As Dictionary(Of String, Action(Of Constants.OptionsEntryStructure, ILayoutElements)) = New Dictionary(Of String, Action(Of Constants.OptionsEntryStructure, ILayoutElements)) From
{
{Constants.KeyLayoutType, AddressOf KeyLayoutType}
}
Sub MakeLayOuts
Dim LayoutElement As ILayoutElements = Nothing
Dim Value = "SomeValues"
Dim key = "Key"
LayoutHandler(key)(Value, LayoutElement)
' LayoutElement remains nothing.....
End Sub
Protected Sub KeyLayoutType(elem As Constants.OptionsEntryStructure, Layout As ILayoutElements)
Layout = New LayoutObject 'which would implement the interface
End Sub
You need to declare the parameter as ByRef if you want to alter the object to which the variable in the calling code points to:
Protected Sub KeyLayoutType(elem As Constants.OptionsEntryStructure, ByRef Layout As ILayoutElements)
Layout = New LayoutObject 'which would implement the interface
End Sub
This is true with any reference type (classes). The fact that they are referenced with an interface makes no difference.
If you can't use ByRef, and you can't use a function to return the new object, then your only other real option would be to request a type of object which has the layout object as a property. For instance:
Public Interface ILayoutElementContainer
Public Property LayoutElement As ILayoutElements
End Interface
Protected Sub KeyLayoutType(elem As Constants.OptionsEntryStructure, Container As ILayoutElementContainer)
Container.LayoutElement = New LayoutObject 'which would implement the interface
End Sub

Cast array in Object variable to type in System.Type variable

I have this function:
Public Sub DoStuff(ByVal type as System.Type, ByVal value as Object)
End Sub
The 'value' argument is always an array of the same type as 'type'. How can I loop through the values of the array?
I'd like to be able to do something like this:
DoStuff(GetType(Integer), New Integer(){1,2,3})
Public Sub DoStuff(ByVal type as System.Type, ByVal value as Object)
//Strongly types arr as Integer()
Dim arr = SomeCast(type, value)
For Each i in arr
//Do something with i
Next
End Sub
Edit Ok, I think I'll add more details so you can see what I'm trying to do. I have an object that captures values coming back from another page. Once I have them captured, I want to loop through the 'Values' property. So DoStuff() above would be called for each dictionary object in 'Values'. If the value in the dictionary objct is an array I want to loop through it as well. I was saving the type added through the generic function call as a System.Type, but maybe that's not the way to go. How can I write this so I can save the type of the array and loop through the array later?
Public Class PopUpReturnValues
Implements IPopUpReturnValues
Public Sub AddValue(Of T As Structure)(ByVal name As String, ByVal value As T) Implements IPopUpReturnValues.AddValue
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(T), .Value = value, .IsArray = False})
End Sub
Public Sub AddArray(Of T As Structure)(ByVal name As String, ByVal values As T()) Implements IPopUpReturnValues.AddArray
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(T), .Value = values, .IsArray = True})
End Sub
Public Sub AddStringValue(ByVal name As String, ByVal value As String) Implements IPopUpReturnValues.AddStringValue
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(String), .Value = value, .IsArray = False})
End Sub
Public Sub AddStringArray(ByVal name As String, ByVal values As String()) Implements IPopUpReturnValues.AddStringArray
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(String), .Value = values, .IsArray = True})
End Sub
Private _values As New Dictionary(Of String, PopUpReturnValue)
Public ReadOnly Property Values() As IDictionary(Of String, PopUpReturnValue)
Get
Return _values
End Get
End Property
Public Class PopUpReturnValue
Public UnderlyingType As Type
Public Value As Object
Public IsArray As Boolean
End Class
End Class
Comments moved to answers per OP
Your "do something" code in based on the type I assume, String vs Int vs Apple, it would need to handle all three types with an If statement. Just include an overload for those three types, you don't actually need to pass the type information. However, if its just calling ToString() then just use an Object array.
And if you don't like overloads, just use the TypeOf operator to inspect the values of the array. When you throw an Integer into an Object array, its still an Integer, just a boxed one.
Is the type known at compile time? If so, perhaps you could use Generics.
You can provide an Action, like this:
Public Sub DoStuff(ByVal value As Array, ByVal process As Action(Of Object) )
For Each item In value
process(item)
Next item
End Sub
Then you just need a method that takes one parameter for each of the types you care about and knows how to cast object to that type. Then call DoStuff() passing in the address of that method. You could even use a lambda if you wanted.

Switching Byref to Byval on method calls VB.NET

Switching Byref to Byval on method calls
I have many warnings raised due to:
"Implicit conversion from xxxx to yyyy in copying the value of 'ByRef' parameter zzzz back to the matching argument."
My feeling is that it would be safe to change the function parameters from byref to byval as nothing special is being done with the reference type pointers inside these methods the reference types are simply being used and I think the behaviour would be exactly the same if running with a copy a pointer rather than the original.
Another consideration is that I have two classes which inherit from a base class. The same situation is occuring in that the byref params are causing implicit casting from the base class to the narrower concrete class. Again I can't see any problems with this code running byval either.
Does anyone have any tips regarding use of parameters in functions when dealing with reference types?
Some of the other things that are currently being passed around byref in my project are database connection objects i.e. OracleConnection and SqlConnection. Is there any good reason for passing these around byref?
Example 1
Implicit conversion from 'Object' to 'Integer' in copying the value of 'ByRef' parameter 'value' back to the matching argument.
Calling code:
cmd = New SqlCommand()
cmd.Parameters.Add(CreateParameter("Alpha", SqlDbType.Int,ParameterDirection.Input, -1, AlphaValue))
Function:
Private Function CreateParameter(ByVal parameterName As String, ByVal dbType As SqlDbType, ByVal direction As ParameterDirection, ByVal size As Integer, ByRef value As Object) As SqlParameter
Dim retParam As SqlParameter
retParam = New SqlParameter(parameterName, dbType)
retParam.Direction = direction
retParam.Size = size
retParam.Value = value
Return retParam
End Function
Example 2
Implicit conversion from 'System.Data.IDataReader' to 'System.Data.SqlClient.SqlDataReader' in copying the value of 'ByRef' parameter 'reader' back to the matching argument.
Calling code:
Dim reader As new SqlDataReader
ReleaseReader(reader)
Method:
Public Sub ReleaseReader(ByRef reader As IDataReader)
If reader IsNot Nothing Then
If Not reader.IsClosed Then
reader.Close()
End If
reader.Dispose()
End If
End Sub
When defining a method in VB.Net, or C# for that matter, the you should pass parameters by value (ByVal) unless you need to take advantage of ByRef semantics. If you are not resetting the parameter value within the method then you should definitely turn these into ByVal calls.
If you are resetting the reference but not taking advantage of it from the call site then I would write a helper method which takes the parameter ByVal and calls into the one taking it ByRef. This will remove the warning because the resulting code won't be subject to narrowing conversion errors.
For example:
Public Sub ExampleMethod(ByRef p1 As Object)
p1 = "foo"
End Sub
Public Sub ExampleMethodWrapper(ByVal p1 as Object)
ExampleMethod(p1)
End Sub
Public Sub Test()
Dim v1 As String = "hello"
Dim v2 As String = "world"
ExampleMethod(v1) ' Warning generated
ExampleMethodWrapper(v2) ' No warning
End Sub