expression.call value cannot be null - vb.net

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".

Related

Runtime Generated EventHandler of Unknown Type

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.

ArgumentException calling Expression.IfThenElse

I'm trying to build this LINQ query:
Result = Result.Where(Function(Row) If(IsDBNull(Row(7)), False, Convert.ToInt32(Row(7)) > 10))
Result is a IEnumerable(Of Object()).
I manage to build the expression with this code, but at the last line, I get an error message.
The code I have is this:
Dim whereMethod = GetType(Queryable).GetMethods(BindingFlags.Public Or BindingFlags.Static).First(Function(m) m.Name = "Where").MakeGenericMethod(GetType(Object()))
Dim convertMethod As MethodInfo = Nothing
Dim rowParameter = Expression.Parameter(GetType(Object()), "Row")
Dim isdbnullMethod As MethodInfo = GetType(System.Convert).GetMethod("IsDBNull", New Type() {GetType(Object)})
Dim expr As Expression = Nothing
Dim tempexpr As Expressions.LambdaExpression = Nothing
convertMethod = GetType(System.Convert).GetMethod("ToInt32", New Type() {GetType(Object)})
tempexpr = Expression.Lambda(Expression.IfThenElse(
Expression.Call(isdbnullMethod,
Expression.ArrayAccess(rowParameter, Expression.Constant(7))),
Expression.Constant(False),
Expression.GreaterThan(
Expression.Call(
convertMethod,
Expression.ArrayAccess(rowParameter, Expression.Constant(7))),
Expression.Constant(10))),
rowParameter)
Then I call:
expr = Expression.Call(whereMethod, Result.AsQueryable.Expression, Expression.Lambda(tempexpr.Body, rowParameter))
And at this line I get this error:
What can be the problem? Without the IfThenElse it works. Also this:
Result = Result.Where(Function(Row) Convert.ToInt32(Row(7)) > 10)
EDIT
Is this because the If operator is an "Action" method and doesn't returns a value?
Btw. the Expression.IfThenElse uses the IIf function. How could I use the If function?
EDIT II
I think, I found it: Expression.Condition. It uses IIf too, but with this, I don't get an exception.
Your Edit II is correct: Expression.IfThenElse returns void, making the whole expression an Action. Expression.Condition returns whatever the type is in the ifTrue parameter, making your expression Expression(Of Func(Of Boolean)), which is what you want.
As an aside, I don't believe it's really calling the IIf function. That's simply a debug view of what is going on. I don't think it's really calling either of those VB.NET-only methods

call First LINQ method

I have this:
Dim aggregator_func As MethodInfo = Nothing
aggregator_func = GetType(Enumerable).GetMethods(BindingFlags.Public Or BindingFlags.Static).
Where(Function(m) m.Name = "First").Where(Function(m) m.ReturnType.FullName = "")(0).MakeGenericMethod(GetType(Object))
Dim groupparameter = Expression.Parameter(GetType(Linq.IGrouping(Of Object(), Object())), "g")
Dim aggregation As Expression
I would like to call this: g.First()(0)
g.First returns an object array, but I need only the object at the specified index (in this case 0). I can easily provide the index with a constant, but how could I call the above expression?
I have googled, but found nothing useful to me.
This should be complement somehow:
aggregation = Expression.Call(aggregator_func, groupparameter)
Thanks.
EDIT:
The g parameter is an Linq.IGrouping(Of Object(), Object()). That' why First returns an array of objects. Maybe, an array of objects can be called an object too, but I think, this is not important now.
As explained here, static methods like Enumerable.First need a null instance passed as the first argument. You also need to construct the Generic Method First properly. If you don't, the compiler won't recognize the type of aggregation as Object(), rather just as Object, and it won't compile
Only line changed is the aggregator_func line, added last two lines.
Dim aggregator_func As MethodInfo = Nothing
aggregator_func = GetType(Enumerable).GetMethods(BindingFlags.Public Or BindingFlags.Static).
Where(Function(m) m.Name = "First").Where(Function(m) m.ReturnType.FullName = "")(0).MakeGenericMethod(GetType(Object()))
Dim groupparameter = Expression.Parameter(GetType(Linq.IGrouping(Of Object(), Object())), "g")
Dim aggregation As Expression = Expression.Call(Nothing, aggregator_func, groupparameter)
Dim arrayAccess As Expression = Expression.ArrayAccess(aggregation, Expression.Constant(0))

variable is referenced from scope, but not defined LINQ expression tree

I'm trying to get this LINQ expression:
Result = Result.Where(Function(Row) _WhereExpressions(0).InElements.Contains(Convert.ToString(Row(0))))
I have this code for it:
convertMethod = GetType(System.Convert).GetMethod("ToString", New Type() {GetType(Object)})
containsMethod = GetType(System.Collections.Generic.List(Of String)).GetMethod("Contains", New Type() {GetType(String)})
Dim listParameter = Expression.Parameter(GetType(List(Of String)), "_WhereExpressions(0).InElements")
expr = Expression.Call(whereMethod, Result.AsQueryable.Expression,
Expression.Lambda(Expression.Call(listParameter, containsMethod,
Expression.Call(convertMethod, Expression.ArrayAccess(rowParameter, Expression.Constant(index)))), rowParameter))
I get the desired expression, but if I compile, I get the error:
variable '_WhereExpressions(0).InElements' of type 'System.Collections.Generic.List`1[System.String]' referenced from scope '', but it is not defined
The _WhereExpressions(0).InElements is of course declared.
How can I fix it?
Thanks.
EDIT: here are all the declarations:
Dim whereMethod = GetType(Queryable).GetMethods(BindingFlags.Public Or BindingFlags.Static).First(Function(m) m.Name = "Where").MakeGenericMethod(GetType(Object()))
Dim convertMethod As MethodInfo = Nothing
Dim containsMethod As MethodInfo = Nothing
Dim rowParameter = Expression.Parameter(GetType(Object()), "Row")
The _WhereExpressions(0).InElements is a simple list of string, like this here:
Dim idlist As New List(Of String)
idlist.Add("1")
idlist.Add("2")
I read the linked post, but I can't really figure out, how I should solve my problem.
Expression trees have a lot capability, but looks a bit difficult for me.
EDIT2:
This is an example, what exactly I would like to achieve. Just copy and paste in vs:
Dim dt As New DataTable
dt.Columns.Add("f1", Type.GetType("System.String"))
dt.Columns.Add("f2", Type.GetType("System.Int32"))
For i = 0 To 100
dt.Rows.Add(i.ToString, i * 2)
Next
Dim indexes As New List(Of Integer)
indexes.Add(0)
indexes.Add(1)
Dim lst As New List(Of String)
lst.Add("10")
lst.Add("11")
Dim datarows As New List(Of DataRow)
For i = 0 To dt.Rows.Count - 1
datarows.Add(dt.Rows(i))
Next
Dim result As IEnumerable(Of Object())
result = datarows.Select(Function(row) indexes.Select(Function(index) row(index)).ToArray)
'I would like this as an expression:
result = result.Where(Function(row) lst.Contains(Convert.ToString(row(0))))
EDIT3: I got it:
Dim lst As Expression = Expression.Constant(list, GetType(System.Collections.Generic.List(Of String)))
It is hard to replicate code without knowing the full variables. I think your mistake though is in your understanding of Expression.Parameter. This is mainly used as a way to pass explicit parameters into the lambda: In your example, Row is a good example for when Expression.Parameter should be used. _WhereExpressions isn't an explicit parameter, it is a variable that I assume is in scope that you want to create a closure around.
You should also note that the second variable of the Expression.Parameter method is optional, and for debug purposes only: If you changed it to say: Expression.Parameter(GetType(List(Of String)), "Nonsense.nonsense"), you would probably see the error message change accordingly.
It sounds like you're trying to introduce a closure around _WhereExpressions. Using raw expression trees, this is fairly hard to do. The closest thing to an easy way to do this is to wrap Expression.Constant around _WhereExpressions.InElements. However, you're going to run into trouble if you're executing the compiled expression when _WhereExpressions is out of scope. See Issue with closure variable capture in c# expression.

Expression Lambda to order fields with null value

I use a dynamic method to order a list of object.
For Each Sort In MesDonnees.MesOptions
If Sort.Sens < 2 Then
Dim sortPropertyName As String = Sort.ColName
If (TypeClass.GetProperties().Any(Function(prop) prop.Name = sortPropertyName AndAlso prop.CanRead)) Then
'Information sur la propriété recherchée
Dim pinfo As PropertyInfo = TypeClass.GetProperty(sortPropertyName)
Dim Expr As Expression = Expression.Property(paramExpr, pinfo)
Dim Tostr As Expression = Expression.Call(Expr, "ToString", Nothing)
Dim orderByFunc As Func(Of MaClass, Object) = Expression.Lambda(Of Func(Of MaClass, Object))(Tostr, paramExpr).Compile()
Dim sortFunc As Func(Of IEnumerable(Of MaClass), IOrderedEnumerable(Of MaClass)) = Nothing
If (Not CBool(Sort.Sens)) Then
sortFunc = (Function(source) source.OrderBy(orderByFunc))
Else
sortFunc = (Function(source) source.OrderByDescending(orderByFunc))
End If
query = sortFunc(query).ToList()
End If
End If
Next
Sort.ColName is the name of my field I want to filter.
When I have no null value, it's perfect but when I have a null value, I get an exception in the line query = sortFunc(query).ToList() :
Object reference not set to an instance of an object
I see different code with Expression.Call and ISNullOrEmpty but I don't know how use it correctly in my case. I want null or Empty are in first like a "".
Thanks for your help and explanation
You can change your toStr expression to include a Coalesce operation where the right expression is a constant. Note that the left expression needs to be reference type (or a nullable value type) so you also need to ensure that the property type is not a value type.
Here's an example that should work:
Dim toStr As Expression = Expression.Call(
If(pinfo.PropertyType.IsValueType, expr, Expression.Coalesce(expr, Expression.Constant(String.Empty))),
"ToString",
Nothing
)