Yield with delegate - vb.net

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

Related

Call a generic function passed as parameter in recursive function

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

List of IEnumerables

Please see the code below, which was written by someone else and works very well:
Public Function GetMembers(Optional ByVal sortExpression As String = "MemberId ASC") As List(Of Member) Implements IMemberDao.GetMembers
Dim sql As String =
" SELECT MemberId, Email, CompanyName, City, Country" &
" FROM [Member] ".OrderBy(sortExpression)
Return db.Read(sql, Make).ToList()
End Function
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)
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"))
}
I understand that Make is a delegate that populates the Member objects with values, but I do not understand how a list of Persons is returned by the Read function? (a list is returned and works very well).
it does this by the yield keyword. This is generally how it works when you are iterating over a collection. Take a look here:
http://msdn.microsoft.com/en-us/library/vstudio/hh156729.aspx
This should give you a good/simple understanding.

Type of Generic seems worthless

Please see the code below:
Public Function ExecuteDynamicQuery(Of T As New)(ByVal sql As String, ByVal type As T) As List(Of T) Implements IGenie.ExecuteDynamicQuery
Dim iConnectionBLL As iConnectionBLL = New clsConnectionBLL
Dim paramValues() As DbParameter = New clsParameterValues().getParameterValues()
Using conn As DbConnection = iConnectionBLL.getDatabaseTypeByDescription("Genie2"), _
rdr As DbDataReader = clsDatabaseHelper.ExecuteReader(conn, CommandType.Text, sql, paramValues)
Dim list As List(Of T) = New List(Of T)
While rdr.Read()
Dim hello As New T
Dim method As MethodInfo = GetType(clsType).GetMethod("PopulateDataReader")
method.Invoke(hello, New Object() {rdr})
list.Add(hello)
End While
Return list
End Using
End Function
Is there a way of executing the SQL statement above without passing in type as an arguement. It seems a bit pointless - the only reason it is there is to let the function know the type of the generic.
Well you can change the method to not have the second parameter:
Public Function ExecuteDynamicQuery(Of T As New)(ByVal sql As String) As List(Of T) Implements IGenie.ExecuteDynamicQuery
However:
You'd need to change IGenie as well
The caller would then need to explicitly specify the type argument, instead of letting the compiler infer it on the basis of the argument (which would no longer be present)

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

vb.net: calling constructor when using generics

I'm not sure if this is possible or not.
I have a number of different classes that implement interface IBar, and have constructors that take a couple of values. Rather than create a bunch of almost identical method, is it possible to have a generic method that will create the appropriate constructor?
private function GetFoo(Of T)(byval p1, byval p2) as List(Of IBar)
dim list as new List(Of IBar)
dim foo as T
' a loop here for different values of x
foo = new T(x,p1)
list.Add(foo)
' end of loop
return list
end function
I get:
'New' cannot be used on a type parameter that does not have a 'New' constraint.
Unfortunately not - .NET generics only allow you to constrain a generic type to have a parameterless constructor, which you can then call with New T()... you can't specify a particular set of parameters.
If you don't mind making your types mutable, you could create an interface which containing a method with the relevant parameters, make all your types implement the interface, and then constrain the type to implement that method and have a parameterless constructor, but it's not ideal.
Another option is to pass in an appropriate Func which takes x and p1 and returns a new T each time. That would certainly be easy to use from C# - not quite so easy in VB IIRC, but worth considering nevertheless.
Expanding on Jon Skeet's answer, here's a possible solution using a Func parameter:
Private Function GetFoo(Of T As IBar)(ByVal p1 As Object, ByVal p2 As Object, ctor As Func(Of Integer, Object, T)) As List(Of IBar)
Dim list As New List(Of IBar)
Dim foo As T
For x = 1 To 10
foo = ctor(x, p1)
list.Add(foo)
Next
Return list
End Function
usage would be similar to
GetFoo(1, 2, Function(i, o) New BarImpl(i, o))
It is possible to cal, a constructor even if it is not specified in generic constraints. See the example below.
'This base class has no constructor except the default empty one
Public Class MyBaseClass
End Class
'this class inhetits MyBaseType, but it also implements a non empty constructor
Public Class MySpecializedClass
Inherits MyBaseClass
Public Sub New(argument As String)
End Sub
End Class
Public Function CreateObject(Of ClassType As MyBaseClass)(argument As String) As ClassType
'First, get the item type:
Dim itemType As Type = GetType(ClassType)
'Now we can use the desired constructor:
Dim constructor As ConstructorInfo = itemType.GetConstructor(New Type() {GetType(String)})
If constructor Is Nothing Then
Throw New InvalidConstraintException("Constructor ""New(String)"" not found.")
Else
Dim result As ClassType = constructor.Invoke(New Object() {argument})
Return result
End If
End Function
Public Sub RunTest()
Try
Console.WriteLine("+----------------------------------------------------+")
Console.WriteLine("Trying to create a instance of MyBaseClass")
Console.WriteLine("+----------------------------------------------------+")
Dim myobject As MyBaseClass = CreateObject(Of MyBaseClass)("string value")
Console.WriteLine(myobject)
Console.WriteLine("Instance of MyBaseClass created")
Catch ex As Exception
Console.WriteLine(ex)
End Try
Try
Console.WriteLine("+----------------------------------------------------+")
Console.WriteLine("Trying to create a instance of MySpecializedClass")
Console.WriteLine("+----------------------------------------------------+")
Dim myobject As MyBaseClass = CreateObject(Of MySpecializedClass)("string value")
Console.WriteLine(myobject)
Console.WriteLine("Instance of MySpecializedClass created")
Catch ex As Exception
Console.WriteLine(ex)
End Try
End Sub
Here is my answer.
Public CreateObject(Of T)() As T
Dim newObj = Activator.CreateInstance(GetType(T), YourParameterHere)
Return newObj
End Function
This will give you the new object. You can pass any parameters to this function.