Call a generic function passed as parameter in recursive function - vb.net

My desire is to run a given function by name through AddressOf with one input parameter, e.g. Function Foo(x as Integer) As Integer. The two inputs I need into the recursive function are the function name _name As String and an object of some type t _list As t (Integer, Double, List(Of Integer), etc). The goal is to process either an element or list of elements with the function name, as there are multiple times I need to process a list by a given function and I do not wish to replicate the list processing code in each location. The ways I've tried to call my best go at this type of function (below) that didn't crash completely resulted in this error:
Warning: List.Test operation failed. Overload resolution failed because no Public 'ProcessList' can be called with these arguments:
'Public Shared Function ProcessList(Of t)(_func As Func(Of Object,t), _list As System.Object) As IEnumerable(Of t)':
Type argument inference fails for argument matching parameter '_func'.
Iterator Function ProcessList(Of t)(_func As Func(Of Object, t), _list As Object) As IEnumerable(Of t)
If _list.GetType = GetType(List(Of t)) Then
Yield _list.SelectMany(Function(l) ProcessList(_func, l))
Else
Yield _func(_list)
End If
End Function
For reference, I found a snippet of Python code that effectively does what I need, but I'm a little rusty on translating in this direction (Python to VB.net), and I'm not as familiar with this type of programming in VB.net. The Python snippet is:
def ProcessList(_func, _list):
return map(lambda x: ProcessList(_func, x) if type(x)==list else _func(x), _list)
Any help as to how I need to call this function, or how to rework this function if my approach is flawed, would be greatly appreciated!
Update:
I re-examined how I was calling the function and a few other things based on #djv's info that my method is working. First, due to the nature of how I'm interfacing with these functions, I have to expose the above function with:
Public Shared Function Foo(ByVal _input As Object) As Object
Return Utilities.ProcessList(AddressOf Bar, _input)
End Function
I'm also now getting the error message:
Warning: List.Test operation failed.
Unable to cast object of type 'System.Int32' to type 'System.Collections.Generic.IList`1[System.Int32]'.
The issue at this point probably lies with the method in which I'm calling my ProcessList function, rather than the function itself as I thought. I'm interfacing with a GUI that is not happy with calling ProcessList on its own, so I need this intermediate "helper" function, which I am apparently not using correctly.

You will always get an IEnumerable(Of T) and T can either be a primitive (i.e. Integer) or list of primitive (i.e. List(Of Integer)). So when you try to call it with a List, you get a List(Of List(Of Integer)) for example.
We can see why by breaking ProcessList up into two methods. The difference between them is the type of the second argument which is either T or IEnumerable(Of T)
Sub Main()
Dim i As Integer = 1
Dim li As New List(Of Integer) From {1, 1, 1}
Dim ri As IEnumerable(Of Integer) = ProcessList(AddressOf foo, i).ToList()
Dim rli As IEnumerable(Of Integer) = ProcessList(AddressOf foo, li).ToList()
Dim d As Double = 1.0#
Dim ld As New List(Of Double) From {1.0#, 1.0#, 1.0#}
Dim rd As IEnumerable(Of Double) = ProcessList(AddressOf foo, d).ToList()
Dim rld As IEnumerable(Of Double) = ProcessList(AddressOf foo, ld).ToList()
Console.ReadLine()
End Sub
Function ProcessList(Of T)(f As Func(Of T, T), p As IEnumerable(Of T)) As IEnumerable(Of T)
Return p.Select(Function(i) ProcessList(f, i)).SelectMany(Function(i) i)
End Function
Iterator Function ProcessList(Of T)(f As Func(Of T, T), p As T) As IEnumerable(Of T)
Yield f(p)
End Function
Function foo(param As Integer) As Integer
Return param + 1
End Function
Function foo(param As Double) As Double
Return param + 1.0#
End Function
Previously, I could not even hit the line in your original code which did the SelectMany. Now, it is hit when the proper function is called. I also retooled that call to fit the new function signature.
The overloads are both called, based on the second argument passed them. However, you only need one foo method for each T (either a primitive or its IEnumerable).

Related

Yield with delegate

Please see the code below:
Public Iterator Function Read(Of T)(ByVal sql As String, ByVal make As Func(Of IDataReader, T), ParamArray ByVal parms() As Object) As IEnumerable(Of T)
Using connection = CreateConnection()
Using command = CreateCommand(sql, connection, parms)
Using reader = command.ExecuteReader()
Do While reader.Read()
Yield make(reader) --line 7
Loop
End Using
End Using
End Using
End Function
Private Shared Make As Func(Of IDataReader, Member) =
Function(reader) _
New Member() With {
.MemberId = Extensions.AsId(reader("MemberId")),
.Email = Extensions.AsString(reader("Email")),
.CompanyName = Extensions.AsString(reader("CompanyName")),
.City = Extensions.AsString(reader("City")),
.Country = Extensions.AsString(reader("Country"))
}
Please see line 7. Make populates an object of type Member with values from the data reader row. I have read the following documentation: http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx. The documentation does not seem to explain what happens when you use a delegate i.e. Yield make(datareader), rather than Yield return datareader. Is control passed back to the calling function as well as the delegate (Make)?
Make(reader) is a shortcut for Make.Invoke(reader). I.e., you
invoke the delegate, passing reader as a parameter, which yields a value of type Member.
Then, you return that value using Yield.
It is equivalent to :
...
Do While reader.Read()
Dim myMember As Member = make(reader)
Yield myMember
Loop
...
PS: If you get a compile-time error in your code (you don't say so in your question): This is due to the fact that your method is declared to return an IEnumerable(Of T), when in fact it returns an IEnumerable(Of Member).

How get correct type of T

I have some code like this:
Private Shared Function ReStoreFromXML(Of T)(ByVal TargetType As T, ByVal XMLpath As String) As List(Of T)
If Not TypeSupported(TargetType) Then Return Nothing
....
Return CType(mySerializer.Deserialize(fstream), List(Of T))
TargetType is, for example, MyCustomType.
TypeSupported should check if TargetType is ok. When I try something like
TargetType.GetType
Or
GetType(T)
I get only System.RuntimeType or System.Type. How can I fix this issue?
UPD:
For more clearly understanding what I want... also in method ReStoreFromXML I have such code:
Dim mySerializer As XmlSerializer
mySerializer = New XmlSerializer(GetType(T))
How can I create mySerializer with argument MyCustomType?
I call my function in such way viewsList = ReStoreFromXML(GetType(MyCustomType), XMLpath)
That's your problem. If you call ReStoreFromXML(GetType(string), ...), then T will be Type/RuntimeType. If you call ReStoreFromXML("somestring", ...), T will be string.
So just remove the first parameter, as it's unnecessary since you already know the type by calling GetType(T).
Private Shared Function ReStoreFromXML(Of T)(XMLpath As String) As List(Of T)
Dim mySerializer = New XmlSerializer(GetType(T))
...
End Function
ReStoreFromXML(Of MyCustomType)(XMLpath)
The type should be the type argument to the function, not an argument of that type. (Yes, it's confusing).
This way you are stating the type twice, so a reasonable call will be:
ReStoreFromXML(Of String)("somestring", xmlPath)
Where the "somestring" is only used to check that it's indeed a string, and that's already stated in the (Of String) part.
You should change the signature of the method to:
Private Shared Function ReStoreFromXML(Of T)(ByVal XMLpath As String) As List(Of T)
If Not TypeSupported(T) Then Return Nothing
...
End Function

Can I specify Queryable.GroupBy (instead of Enumerable.GroupBy) in query expression syntax

When I write a Group By in query expression syntax, the compiler automatically picks Enumerable.GroupBy as my intended tareget method and I get an IEnumerable back instead of an IQueryable. That means a subsequent g.Sum call (inside of my Select) expects a Func(Of TSource, int) instead of an Expression(Of Func(Of TSource, int)). Is there a way to force the Group By to use Queryable.GroupBy instead and give me back an IQueryable?
Contrived Sample Code
Dim myQuery = From x In DataSource.Items
Group By x.Key Into g = Group
Select New With {
.Key = Key,
.RedItems = g.Sum(ItemsOfColor(Colors.Red)) '<== invalid because g.Sum expects a lambda
}
Private Function PurpleItems(color As Integer) As Expression(Of Func(Of Item, Integer))
Return Function(item) If(item.Color = color, 1, 0)
End Function
Why would I want to do this?
The compiler automatically converts between a lambda and an expression based on the target variable type (ie, both Dim f As Func(Of String, Integer) = Function(x) x.Length() and Dim e As Expression(Of Func(Of String, Integer)) = Function(x) x.Length() are valid) so there is no noticable difference in the code between an IEnumerable and IQueryable.
The problem is, LINQ to Entities (and I assume other db backed LINQ implementations) relies on expression trees to translate into SQL. That means the IEnumerable lambda version will not work against an IDbSet as I found in this old question.
The problem is that Queryable.GroupBy() returns IQueryable<IGrouping<TKey, TSource>>, where IGrouping<TKey, TSource> implemens IEnumerable<TSource>, but not IQueryable<TSource>.
And I believe your code wouldn't work anyway, because ItemsOfColor() wouldn't be actually called. Instead, the EF would get an expression that calls ItemsOfColor(). And since it doesn't know that method, it would throw an exception.

Expression filter on IEnumerable

i am trying to apply an expression filter dynamically and can't get it working. Would any of you guys know, how can apply the given expression filter inside the for each loop and then return the object of Type t when it matches?
Public Function FindByCondition( _
filter As Expressions.Expression(Of Func(Of T, Boolean))) As T Implements IRepository(Of T).FindByCondition
Dim metaData As New LinqMetaData
AutoMapper.Mapper.CreateMap(GetType(EntityType), GetEntityType)
Dim dataSource = TryCast(metaData.GetQueryableForEntity(CInt([Enum].Parse(GetType(EntityType), GetEntityType.Name))), IQueryable(Of EntityBase))
Dim q = (From p In dataSource _
Select p).ToList
Dim g = AutoMapper.Mapper.Map(Of IEnumerable(Of T))(q)
For Each k As T In g
k.Equals(filter)
Next
End Function
You need to compile the expression tree to a delegate, then call the delegate on each instance.
Dim compiled As Func(Of T, Boolean) = filter.Compile()
If compiled(k) Then
Or, more simply,
Return g.FirstOrDefault(compiled)
Or, much more simply,
Return AutoMapper.Map(Of T)(dataSource.FirstOrDefault(filter))
This will actually run the filter on the server (or whatever your IQueryable implementation does).
For all of the other cases, you should accept a Func(Of T, Boolean) rather than an expression tree, since you don't actually need the expression tree. Compile() is an expensive call.

VB.NET Calling BeginInvoke on another thread

So from the comments section where this persons code was translated to VB.NET on http://www.codeproject.com/KB/cs/Threadsafe_formupdating.aspx it shows a little code to aid in calling cross thread UI stuff.
<System.Runtime.CompilerServices.Extension()> _
Public Function SafeInvoke(Of T As ISynchronizeInvoke, TResult)(ByRef isi As T, ByRef [call] As Func(Of T, TResult)) As TResult
If isi.InvokeRequired Then
Dim result As IAsyncResult = isi.BeginInvoke([call], New Object() {isi})
Dim endResult As Object = isi.EndInvoke(result)
Return DirectCast(endResult, TResult)
Else
Return [call](isi)
End If
End Function
When I try to call the following however I get an error:
Me.SafeInvoke(Function(x) x.Close())
or
frmLobby.SafeInvoke(Function(x) x.Close())
Error 1 Data type(s) of the type parameter(s) in extension method 'Public Function SafeInvoke(Of TResult)(ByRef call As System.Func(Of frmLogin, TResult)) As TResult' defined in 'GvE.Globals' cannot be inferred from these arguments. Specifying the data type(s) explicitly might correct this error. C:\GvE\GvE\frmLogin.vb 37 9 GvE
What am I missing? I'm calling that code from inside a method defined in a form but that method is being called from another thread.
Just trying to avoid delegates and this is what the code above is supposed to do, but just can't get it to work.
Thanks
Your SafeInvoke method takes a Func(Of T, TResult).
That's a function that takes a T and returns a TResult.
Since x.Close() is a Sub and doesn't return anything, you can't make it into a Func(Of T, TResult).
You should make an overload that takes an Action(Of T) – a sub that takes a T and doesn't return anything.