Exception when casting from concrete class to interface in LINQ query - vb.net

I have a class SomeClass, which can populate itself from a datarow in it's constructor. This class implements IInterface. However, when I execute the code below:
Dim fpQuery As IEnumerable(Of IInterface) = _
From dr As DataRow In DataLayer.SomeMethodToGetADataTable.AsEnumerable _
Select New SomeClass(dr)
I get the error
Unable to cast object of type
'System.Data.EnumerableRowCollection`1[Classes.SomeClass]'
to type
'System.Collections.Generic.IEnumerable`1[Interfaces.IInterface]'
I should probably add that the following code works fine.
Dim fpQuery As IEnumerable(Of SomeClass) = _
From dr As DataRow In DataLayer.SomeMethodToGetADataTable.AsEnumerable _
Select New SomeClass(dr)
As does the simple cast
Dim myInterface As IInterface = New SomeClass(myDataRow)
Any ideas?
EDIT :
Jon Skeet got it spot on. I used the following code and it worked perfectly.
Dim fpQuery2 As IEnumerable(Of IInterface) = fpQuery.Cast(Of IInterface)

You're running into a lack of variance in generics. To put it in a simpler example, you can't treat IEnumerable(Of String) as IEnumerable(Of Object).
The simplest thing would probably be to add a call to Cast(Of TResult).

Related

VB.NET linq extension methods such as ToList not working with generics

In a VB.NET class file, I'm trying to use extension methods such as ToList(), where the generic parameter is populated with what I know to be the subclass, in this context.
Dim specificOrders = request.Orders _
.Where(Function(x) x.GetType().Equals(GetType(SpecificOrderType))) _
.ToList(Of SpecificOrderType)()
However, I'm getting the error message "extension method is not generic or has no type parameters available". Any ideas as to why this is?
This method should be in the System.Linq namespace - I have it open and referenced in the file.
You don't use Where to filter by type. That's what the OfType method is for. It filters and casts:
Dim specificOrders = request.Orders.
OfType(Of SpecificOrderType)().
ToList()
In that case, OfType returns an IEnumerable(Of SpecificOrderType) and calling ToList on that returns a List(Of SpecificOrderType). That's how ToList works. It simply create a List(Of T) with the same generic type as the IEnumerable(Of T) that it's called on.
If you were going to use Where, you would use Cast to go from the base type to SpecificOrderType:
Dim specificOrders = request.Orders.
Where(Function(x) x.GetType().Equals(GetType(SpecificOrderType))).
Cast(Of SpecificOrderType)().
ToList()
One point to note about OfType is that it will match any item that can be cast as the specified type. That is usually what you want and probably the result that your original code would produce but it's worth noting that any item that was of a type that inherited SpecificOrderType would be excluded by your original code but included using OfType.
ToList() can not have type arguments, because that method is not generic.
So just use ToList()
Dim suborders = orders _
.Where(Function(x) x.GetType().Equals(GetType(nonspecificOrder))) _
.ToList()
Try below example.
'Order class
Public Class order
End Class
'specificOrder class
Public Class specificOrder
Inherits order
End Class
'nonspecificOrder class
Public Class nonspecificOrder
Inherits order
End Class
Usage:
Dim orders As List(Of order) = New List(Of [order])
Dim s1 As specificOrder = New specificOrder()
Dim s2 As specificOrder = New specificOrder()
Dim s3 As specificOrder = New specificOrder()
Dim s4 As specificOrder = New specificOrder()
Dim s5 As nonspecificOrder = New nonspecificOrder()
Dim s6 As nonspecificOrder = New nonspecificOrder()
orders.Add(DirectCast(s1, order))
orders.Add(DirectCast(s2, order))
orders.Add(DirectCast(s3, order))
orders.Add(DirectCast(s4, order))
orders.Add(DirectCast(s5, order))
orders.Add(DirectCast(s6, order))
Dim suborders = orders _
.Where(Function(x) x.GetType().Equals(GetType(nonspecificOrder))) _
.ToList()
Now the two "nonspecificOrder" type objects are returned.

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)

Casting Error when sorting with IComparer

I have an ArrayList of strings of the form "Q19_1_1", "Q19_10_1", "Q19_5_1".
With the normal sort method the list will be sorted as
"Q19_1_1"
"Q19_10_1"
"Q19_5_1"
But I would like to sort it numerically based off the second integer in name and then the third. So I would like:
"Q19_1_1"
"Q19_5_1"
"Q19_10_1"
My Sub:
Dim varSet As New ArrayList
varSet.Add("Q19_1_1")
varSet.Add("Q19_10_1")
varSet.Add("Q19_5_1")
varSet.Sort(New VariableComparer())
I have a IComparer:
Public Class VariableComparer
Implements IComparer(Of String)
Public Function Compare(ByVal x As String, ByVal y As String) As Integer Implements System.Collections.Generic.IComparer(Of String).Compare
Dim varPartsX As Array
Dim varPartsY As Array
varPartsX = x.Split("_")
varPartsY = y.Split("_")
Return String.Compare(varPartsX(1), varPartsY(1))
End Function
End Class
But when I attempt to sort I get the error:
Unable to cast object of type 'VBX.VariableComparer' to type 'System.Collections.IComparer'.
VariableComparer implements IComparer but I'm guessing it can't be of type IComparer(Of String)?
How can I resolve this issue? What am I missing?
Using a List(Of String) also gives you access to the LINQ extensions. Specifically the OrderBy and the ThenBy extensions. You could do it something like this:
Dim test3 As New List(Of String)({"Q19_1_1", "Q19_10_1", "Q19_5_1", "Q19_5_2"})
test3 = test3.OrderBy(Of Integer)(Function(s) Integer.Parse(s.ToString.Split("_"c)(1))) _
.ThenBy(Of Integer)(Function(s2) Integer.Parse(s2.ToString.Split("_"c)(2))).ToList
Casting to Integer gives you the proper sorting without using a new IComparer interface
You are correct - the issue is that you implemented IComparer(Of String), but not IComparer, which is a completely different interface.
If you switch to use a List(Of String) instead of ArrayList, it will work correctly.
This will also give you type safety within your collection.
In general, ArrayList (and the other System.Collections types) should be avoided in new development.

Calling Generic function + lambda

I have this function in vb.net that I converted from C# for a project I'm working on.
Private Function GetAllFactory(Of T)(ByVal ctor As Construct(Of T)) As List(Of T)
'TODO: Data Access stuff
Dim ds As New DataSet()
Dim entities = New List(Of T)()
For Each dataRow As DataRow In ds.Tables(0).Rows
Dim entity As T = ctor(dataRow)
entities.Add(entity)
Next
Return entities
End Function
and the following delegate
Private Delegate Function Construct(Of T)(ByVal dataRow As DataRow) As T
I tried converting the code to call the function from C# to vb.net
Return GetAllFactory(Of MyType)(row >= New MyType(row))
the above line doesn't work. I'm sort of stuck. I haven't used lambda much in C# and even less in vb.net.
MyType constructor:
Public Sub New(ByVal dataRow As DataRow)
.
.
.
End Sub
Any suggestions on how to call the GetAllFactory?
You use the Function keyword in VB to write a lambda expression:
Return GetAllFactory(Of MyType)(Function(row) New MyType(row))
Note that >= is a comparison operator while => is the lamda operator in C#. VB might give you some unexpected error message for code using => as it accepts that as an undocumented alias for the >= operator.
VB.Net lambda expressions look like this:
Return GetAllFactory(Of MyType)(Function(row) New MyType(row))

ArrayList Problem in LINQ

i have problem LINQ query.In above,i got error system.object cant be converted to Sytem.String. What can be the problem?
if i use string() instead of ArrayList, it doesn't raise error. But in String(), i should add items manually
Public Shared Function GetCompletionList(ByVal prefixText As String, ByVal count As Integer, ByVal contextKey As String) As String()
Dim movies As New ArrayList()
Dim dt As DataTable = StaticData.Get_Data(StaticData.Tables.LU_TAG)
For Each row As DataRow In dt.Rows
movies.Add(row.Item("DS_TAG"))
Next
Return (From m In movies _
Where m.StartsWith(prefixText, StringComparison.CurrentCultureIgnoreCase) _
Select m).Take(count).ToArray()
End Function
As a general rule, do not ever use ArrayList or any of the other types in System.Collections. These types are deprecated in favour of their generic equivalents (if available) in the namespace System.Collections.Generic. The equivalent of ArrayList happens to be List(Of T).
Secondly, returning an array from a method is generally considered bad practice – although even methods from the framework do this (but this is now widely considered a mistake). Instead, return either IEnumerable(Of T) or IList(Of T), that is: use an interface instead of a concrete type.
You can use List(Of String) instead of ArrayList.
Add a reference to System.Data.DataSetExtensions and do:
return dt
.AsEnumerable() // sic!
.Select(r => r.Item("DS_TAG")) // DataTable becomes IEnumrable<DataRow>
.Where(m => m.StartsWith(prefixText, StringComparison.CurrentCultureIgnoreCase))
.ToArray();
(sorry but that's C# syntax, rewrite to VB.NET as you need)