Runtime Generated EventHandler of Unknown Type - vb.net

Ok, so I have been stuck on this for a while, combining various question solutions from on here.
Situation:
I instantiate an Object of Type x by using the activator.CreateInstance(x)
Type x can vary but always has the same properties and methods.
The problem is that type x has an event ReadCompleted as ReadCompleted (this is a delegate), type y has the same event, with the same delegate but it is of type ReadCompleted1, z => ReadCompleted2, etc...
Solution:
I assign a delegate at runtime using the following code:
Dim Client As Object = ServiceProvider.GetService(TypeDict(Entity))
Dim TaskCompletionSource As New TaskCompletionSource(Of Entity)()
Dim addMethod As MethodInfo = Client.GetType().GetMethod("add_ReadCompleted")
Dim removeMethod As MethodInfo = Client.GetType().GetMethod("remove_ReadCompleted")
Dim self As Object = Nothing
Dim getSelf As Func(Of Object) = Function() self
Dim eventArgType As Type = Client.GetType().GetEvent("ReadCompleted").EventHandlerType.GetMethod("Invoke").GetParameters()(1).ParameterType
Dim e As ParameterExpression = Expression.Variable(eventArgType)
Dim ExpBlock As BlockExpression = Expression.Block({Expression.Parameter(GetType(Object)), e},
Expression.Call(
Nothing,
Me.GetType().GetMethod("ProcessTask"),
Expression.Convert(e, eventArgType),
Expression.Constant(TaskCompletionSource)),
Expression.Call(
Expression.Constant(Client),
removeMethod,
Expression.Convert(
Expression.Invoke(
Expression.Constant(getSelf)),
addMethod.GetParameters()(0).ParameterType)
)
)
Dim lambda As LambdaExpression = Expression.Lambda(
addMethod.GetParameters()(0).ParameterType,
ExpBlock,
Expression.Parameter(GetType(Object)),
Expression.Parameter(eventArgType))
Dim dlg As [Delegate] = lambda.Compile()
self = dlg
addMethod.Invoke(Client, {dlg})
Client.ReadAsync(PrimaryKey)
Now my knowledge on linq and it's Expression class is limited and i did my best to research the msdn documentation but i can't figure it out:
the method ProcessTask gets called properly, for which i've worked long enough, but my parameter e is always Nothing, resulting in a NullReferenceException.
The method:
Public Shared Sub ProcessTask(ByVal e, ByRef tcs)
'Console.WriteLine(e.GetType())
If e.Error IsNot Nothing Then
tcs.TrySetException(e.Error)
ElseIf e.Cancelled Then
tcs.TrySetCanceled()
Else
tcs.TrySetResult(e.Result)
End If
End Sub
I have no idea why, according to how i see it this is the correct way to call my method, but obviously it isn't. Can someone point me in the right direction for this?
Could be I'm just plain blind and missing something very obvious....
Thanks in advance!
EDIT:
Whilst awaiting an answer i tried to do some more debugging(which is hell in this scenario) saw that if i do:
Dim s As ParameterExpression = Expression.Variable(GetType(Object), "Sender")
Dim ExpBlock As BlockExpression = Expression.Block({s, e},
Expression.Call(
Nothing,
GetType(Console).GetMethod("WriteLine", New Type() {GetType(String)}),
Expression.Call(s, GetType(Object).GetMethod("ToString"))),
Expression.Call(
Expression.Constant(Client),
removeMethod,
Expression.Convert(
Expression.Invoke(
Expression.Constant(getSelf)),
addMethod.GetParameters()(0).ParameterType)
))
The sender as object parameter is also nothing, so i'm getting the feeling that none of my arguments are getting passed through...
Hope this helps shed more light on the matter.

I figured it out, turns out i misinterpreted the whole parameter thing of Expression:
Dim Client As Object = ServiceProvider.GetService(TypeDict(Entity))
Dim TaskCompletionSource As New TaskCompletionSource(Of Entity)()
Dim addMethod As MethodInfo = Client.GetType().GetMethod("add_ReadCompleted")
Dim removeMethod As MethodInfo = Client.GetType().GetMethod("remove_ReadCompleted")
Dim self As Object = Nothing
Dim getSelf As Func(Of Object) = Function() self
Dim eventArgType As Type = Client.GetType().GetEvent("ReadCompleted").EventHandlerType.GetMethod("Invoke").GetParameters()(1).ParameterType
Dim s As ParameterExpression = Expression.Parameter(GetType(Object), "Sender")
Dim e As ParameterExpression = Expression.Variable(eventArgType, "EventArgs")
Dim ExpBlock As BlockExpression = Expression.Block(\*{Expression.Parameter('GetType(Object)), e},*\ <--- this has to go
Expression.Call(
Nothing,
Me.GetType().GetMethod("ProcessTask"),
Expression.Convert(e, eventArgType),
Expression.Constant(TaskCompletionSource)),
Expression.Call(
Expression.Constant(Client),
removeMethod,
Expression.Convert(
Expression.Invoke(
Expression.Constant(getSelf)),
addMethod.GetParameters()(0).ParameterType)
)
)
Dim lambda As LambdaExpression = Expression.Lambda(
addMethod.GetParameters()(0).ParameterType,
ExpBlock,
s,
e)
\*Expression.Parameter(GetType(Object)),
Expression.Parameter(eventArgType))*\ <-- this has to change
Dim dlg As [Delegate] = lambda.Compile()
self = dlg
addMethod.Invoke(Client, {dlg})
Client.ReadAsync(PrimaryKey)
So as i see it the parameters don't have to be defined in the expression itself, but they sould be created, and the reference should be kept to use them in the lambda in which you compile the expression.
Hope someone in the future has some use for this answer, and thanks to all for taking a look at this.

Related

Getting value from a custom object exception error

When I try to assign _val I get "Object does not match target type."
I verified both PropertyInfo's are system.string. I also found examples of this syntax. Thanks for any help.
Private Function SetAttributesForSplitFiles(ByVal _file As String, ByVal _depHeader As HeaderParse)
Dim _fileMask As New FileMaskExtension()
Dim _type As Type = GetType(HeaderParse.depBackupFileProperties)
For Each _prop As Reflection.PropertyInfo In GetType(OutputMgr.Interface.FileMaskExtension).GetProperties()
Dim _headerProperty As PropertyInfo = _type.GetProperty(_prop.Name)
Dim _val = _headerProperty.GetValue(_depHeader)
_prop.SetValue(_fileMask, _val, Nothing)
Next
SendFileTODepcon(_fileMask, _file)
End Function
It was because the custom class I was using was two layers deep.
This worked.
Dim _val As Object = _headerProperty.GetValue(_depHeader.FileProperties)

expr. tree: Static method requires null instance, non-static method requires non-null instance

I searched the questions and found some topics and I suspect the error cause, but I can't figure it out.
I would like to build this expression part:
Function(row) groupedindexes.Select(
Function(grpindex) row(grpindex))
I already build the part Function(grpindex) row(grpindex) with this:
Dim fieldselector As Expressions.LambdaExpression
fieldselector = Expression.Lambda(Expression.ArrayAccess(rowParameter, indexParameter), indexParameter)
The declarations are:
Dim rowParameter = Expression.Parameter(GetType(Object()), "Row")
Dim indexParameter = Expression.Parameter(GetType(Integer), "grpindex")
Now, I would like to build the Select part like this:
Dim outerfieldselector As Expressions.LambdaExpression
outerfieldselector = Expression.Lambda(Expression.Call(grpindexes, selectMethod, fieldselector), rowParameter)
The declarations are:
Dim grpindexes As Expression = Expression.Constant(groupedindexes, GetType(System.Collections.Generic.List(Of Integer)))
Dim selectMethod = GetType(Queryable).GetMethods(BindingFlags.Public Or BindingFlags.Static).First(Function(m) m.Name = "Select").MakeGenericMethod(GetType(Object), GetType(System.Func(Of Integer, Object)))
groupedindexes is a normal List(Of Integer).
In runtime, I get the above error at the line outerfieldselector=...
In my opinion, it should work. I call the Select method on grpindexes with one argument (fieldselector).
What could be the problem?
Thanks.
EDIT: a sample project can be downloaded at this link: http://www.filedropper.com/exptree
EDIT II:
here a simple, short console application project:
Imports System.Reflection
Imports System.Linq.Expressions
Module Module1
Dim rowParameter = Expression.Parameter(GetType(Object()), "Row")
Dim indexParameter = Expression.Parameter(GetType(Integer), "grpindex")
Dim expr As Expression = Nothing
Dim groupedindexes As New List(Of Integer)
Dim grpindexes As Expression = Expression.Constant(groupedindexes, GetType(System.Collections.Generic.List(Of Integer)))
Dim selectMethod = GetType(Queryable).GetMethods(BindingFlags.Public Or BindingFlags.Static).First(
Function(m) m.Name = "Select").MakeGenericMethod(GetType(Object), GetType(System.Func(Of Integer, Object)))
Dim fieldselector As Expressions.LambdaExpression
Dim outerfieldselector As Expressions.LambdaExpression
Sub Main()
groupedindexes.Add(0)
groupedindexes.Add(1)
groupedindexes.Add(2)
fieldselector = Expression.Lambda(Expression.ArrayAccess(rowParameter, indexParameter), indexParameter)
outerfieldselector = Expression.Lambda(Expression.Call(grpindexes, selectMethod, fieldselector), rowParameter)
End Sub
End Module
EDIT 3:
I think, I got it with the help of svick.
Dim selectMethod = GetType(Enumerable).GetMethods(BindingFlags.Public Or BindingFlags.Static).First(Function(m) m.Name = "Select").MakeGenericMethod(GetType(Integer), GetType(Object))
The problem is that Queryable.Select() is not an instance method. You call it as if it was one in VB, but it's not, and that's reflected in expression trees.
So, the line should look like this:
outerfieldselector = Expression.Lambda(Expression.Call(Nothing, selectMethod, grpindexes, fieldselector), rowParameter)
Even if you fix that, your code still won't work. Some of the issues are:
MakeGenericMethod() expects the types of the type parameters. Here those are TSource and TResult, which should be Integer and Object.
List(Of Integer) does not implement IQueryable.

expression.call value cannot be null

I'm trying to code this LINQ query with expression trees:
Result = Result.Where(Function(Row) Convert.ToInt32(Row(2)) <= 10)
Result is declared as Dim Result As IEnumerable(Of Object()).
I have this code so far:
Dim queryiabledata As IQueryable(Of Object()) = Result.AsQueryable
Dim pe As ParameterExpression = Expression.Parameter(GetType(String), "Row(2)")
Dim left As expression = Expression.Call(pe, GetType(String).GetMethod("Convert.ToInt32", System.Type.EmptyTypes))
Dim right As Expression = Expression.Constant(10)
Dim e1 As Expression = Expression.LessThanOrEqual(left, right)
Dim predicatebody As Expression = e1
Dim wherecallexpression As MethodCallExpression = Expression.Call(
GetType(Queryable), "Where", New Type() {queryiabledata.ElementType}, queryiabledata.Expression,
Expression.Lambda(Of Func(Of Object(), Boolean))(predicatebody, New ParameterExpression() {pe}))
Result = queryiabledata.Provider.CreateQuery(Of Object())(wherecallexpression)
But if I run the query, I get an ArgumentNullException (Value cannot be null. Parameter name: method) at Expression.Call.
I tried to change "Convert.ToInt32" to "Value", but I got the same error.
How can I fix it?
Are the another code lines right to get the desired result?
I'm more used to C#, though I do VB.NET occasionally. Reflection in VB.NET is downright ugly. Getting the Where method is a bit of a hack. Here's the code:
shortForm and longForm should be identical.
Dim result As IEnumerable(Of Object()) = New List(Of Object())()
Dim queryiabledata As IQueryable(Of Object()) = result.AsQueryable()
Dim shortForm As Expression = queryiabledata.Where(Function(Row) Convert.ToInt32(Row(2)) <= 10).Expression
Dim whereMethod = GetType(Queryable).GetMethods(BindingFlags.Public Or BindingFlags.Static).
First(Function(m) m.Name = "Where").
MakeGenericMethod(GetType(Object()))
Dim convertMethod = GetType(System.Convert).GetMethod("ToInt32", New Type() {GetType(Object)})
Dim rowParameter = Expression.Parameter(GetType(Object()), "Row")
Dim longform As Expression =
Expression.Call(
whereMethod,
queryiabledata.Expression,
Expression.Lambda(
Expression.LessThanOrEqual(
Expression.Call(
convertMethod,
Expression.ArrayAccess(
rowParameter,
Expression.Constant(2)
)
),
Expression.Constant(10)
),
rowParameter
)
)
result = queryiabledata.Provider.CreateQuery(longform)
This line seems suspect to me:
GetType(String).GetMethod("Convert.ToInt32", System.Type.EmptyTypes)
GetType(String) returns the runtime type String. You then try to get a method called "Convert.ToInt32" which doesn't exist on the string type. I suspect that is returning null which is the source of your exception.
Perhaps you need to use something like this:
GetType(Convert).GetMethod("ToInt32", new Type() {GetType(Object)})
Since there are multiple overloads of the ToInt32 method of the Convert class, you need to specify which of the overloads you want by providing an array of Type as the second parameter. In other words, you a saying "give me the overload that takes an Object type as it's parameter".

Create Object by using Lambda Expression based on Class Name

I refer to the link below:
http://rogeralsing.com/2008/02/28/linq-expressions-creating-objects/
Is it possible to enhance it to create object by providing the class name?
Basically to achieve what Activator.CreateInstance could do below by using Lambda Expression
Dim typeName As String = "Testing.ClassNameth, Testing"
Dim typ As Type = Type.GetType(typeName)
Dim testObj = Activator.CreateInstance(typ)
Dim methodInfo As MethodInfo = testObj.GetType().GetMethod("TestPrintObject")
The purpose on why I am doing it is because the Activator.CreateInstance's performance is quite slow.
Thanks!
Ok. After few researches, below is the answer:
Add the functions below to any of your related class:
Public Function GetActivator(Of T)(type As Type) As ObjectActivator(Of T)
'make a NewExpression that calls the
'ctor with the args we just created
Dim newExp As NewExpression = Expression.[New](type)
'create a lambda with the New
'Expression as body
Dim lambda As LambdaExpression = Expression.Lambda(GetType(ObjectActivator(Of T)), newExp)
'compile it
Dim compiled As ObjectActivator(Of T) = DirectCast(lambda.Compile(), ObjectActivator(Of T))
Return compiled
End Function
Public Delegate Function ObjectActivator(Of T)() As T
Then we can call the function above as:
Dim typeName As String = "Testing.ClassNameth, Testing"
Dim typ As Type = Type.GetType(typeName)
Dim testObj As Object = YourHelperClassInstance.GetActivator(Of Object)(typ).Invoke()
Hope this post would help someone in the future.
Thanks!

Strange error reported in this code, please explain?

I put together this bit of code from a few other samples, and I am getting an error I cant understand. On this line in the code below, on the word Observer,
Dim Results As ManagementObjectCollection = Worker.Get(Observer)
I get the error
"Value of type 'System.Management.ManagementOperationObserver' cannot be converted to 'Integer'"
Can somebody explain what this means?
There are two signatures for ManagementObjectSearcher.Get(), one has no parameters and the other has one parameter, a ManagementOperationObserver for async operation. That is what I am providing, yet the error indicates conversion involving an integer?
Public Shared Sub WMIDriveDetectionASYNC(ByVal args As String())
Dim Observer As New ManagementOperationObserver()
Dim completionHandler As New MyHandler()
AddHandler Observer.Completed, AddressOf completionHandler.Done
Dim Machine = "192.168.0.15"
Dim Scope = New ManagementScope("\\" & Machine & "\root\cimv2")
Dim QueryString = "select Name, Size, FreeSpace from Win32_LogicalDisk where DriveType=3"
Dim Query = New ObjectQuery(QueryString)
Dim Worker = New ManagementObjectSearcher(Scope, Query)
Dim Results As ManagementObjectCollection = Worker.Get(Observer) 'use parameter to make async
For Each item As ManagementObject In Results
Console.WriteLine("{0} {2} {1}", item("Name"), item("FreeSpace"), item("Size"))
Dim FullSpace As Long = (CLng(item("Size")) - CLng(item("FreeSpace"))) \ 1000000
Console.WriteLine(FullSpace)
Next
End Sub
Public Class MyHandler
Private _isComplete As Boolean = False
Public Sub Done(sender As Object, e As CompletedEventArgs)
_isComplete = True
End Sub 'Done
Public ReadOnly Property IsComplete() As Boolean
Get
Return _isComplete
End Get
End Property
End Class
Thanks for any advice!
I think that uses a reference type to get the result and put it in the object you sent as a parameter. So I think it just needs to look like:
Worker.Get(Observer)
instead of trying to set something = to that since it isn't a function that returns a value.
Then use the events you hook up to the object to handle whatever you need to do with the items you find.