Generic extension method, List(Of T) - vb.net

I am simply trying to spit out a string which aggregates a List's values into a NewLine delimited string.
<Extension()>
Public Function ToColumn(Of T)(ByVal source As IEnumerable(Of T)) As String
' assuming Aggregate(Of String) will be inefficient, using StringBuilder
Dim sb As New StringBuilder()
For Each val As T In source
sb.Append(val.ToString() & Environment.NewLine)
Next
Return sb.ToString()
End Function
This method will help while debugging to see the entire contents of a List, while ?source only spits out the first 100 elements in the immediate window (maybe there is a different way to do this?). I just want to be able to copy it over to something like Excel quickly for analysis.
The problem is that although this method compiles, in usage, it is not recognized as an extension of List(of T) i.e.
Dim result As Int32()
Debug.Print(result.ToList().ToColumn())
' this line doesn't compile:
' 'toColumn' is not a member of 'System.Collections.Generic.List(Of Integer)'
I'd like to reuse this method on anything which I can box into an Object and get .ToString() (everything!), hence the generic aspect of it. I have seen many people add constraints to the generic T, i.e. Where T : Constraint, but my constraint would be Object, as is the nature of this method, which doesn't compile.
My question is why isn't it recognized as an extension of List(Of Integer). Also, is what I want to achieve possible?

Have you tried using an extension method on List(Of T) instead of IEnumerable(Of T), like this:
<Extension()>
Public Function ToColumn(Of T)(ByVal source As List(Of T)) As String
' assuming Aggregate(Of String) will be inefficient, using StringBuilder
Dim sb As New StringBuilder()
For Each val As T In source
sb.Append(val.ToString() & Environment.NewLine)
Next
Return sb.ToString()
End Function

Related

How to mimic Java's Wildcard types in VB.net?

I have an interface which I defined like this:
Public Interface ISomething(Of T)
' methods
End Interface
I now did an implementation:
Public Class ConcreteThing
Implements ISomething(of SomeClass)
' Implementation
End Class
I have multiple such concrete implementations, and want to have a function which returns any of them based on its parameters. In Java, I would do something like this:
public ISomething<?> getSomething(ParamType p) {
if(p.hasFoo()) return new ConcreteThing();
if(p.hasBar()) return new OtherConcreteThing();
throw new IllegalStateException("p neither has Foo nor Bar");
}
I already searched about this issue and found out that VB.net does not have wildcard types, so I tried:
Public Function GetSomething(p as ParamType) as ISomething(Of Object)
If p.HasFoo Then Return New ConcreteThing()
If p.HasBar Then Return New OtherConcreteThing()
Throw New InvalidOperationException("p neither has Foo nor Bar")
End Function
This compiles, but I get the warning: Runtime errors might occurr when converting 'Foo.ConcreteThing' to 'Foo.ISomething(Of Object)'.
When I try the following, as suggested in a similar question:
Public Function GetSomething(Of T)(p as ParamType) as ISomething(Of T)
If p.HasFoo Then Return New ConcreteThing()
If p.HasBar Then Return New OtherConcreteThing()
Throw New InvalidOperationException("p neither has Foo nor Bar")
End Function
the warning only changes to Runtime errors might occurr when converting 'Foo.ConcreteThing' to 'Foo.ISomething(Of T)'.
So, how do I get this right? Or, if this indeed IS right, how do I have Visual Studio ignore this warning?
I investigated on this issue a little more, discussed it with my colleagues, and I think I found the solution / reason for the warnings.
The warning message is a bit hard to understand and unconcise. What they are trying to say is that, as silly as it sounds, covariance does not work as expected for primitive types, even when using the Out keyword!
Consider an excerpt from this example on MSDN:
' Covariance.
Dim strings As IEnumerable(Of String) = New List(Of String)()
' An object that is instantiated with a more derived type argument
' is assigned to an object instantiated with a less derived type argument.
' Assignment compatibility is preserved.
Dim objects As IEnumerable(Of Object) = strings
This works. Now, change the first IEnumerable to IList:
Dim strings As IList(Of String) = New List(Of String)()
Dim objects As IEnumerable(Of Object) = strings
Works, too. OK, we are lucky, let's change the second:
Dim strings As IList(Of String) = New List(Of String)()
Dim objects As IList(Of Object) = strings
Boom, InvalidCastException. Looking at the signature, this is because the generic parameter in IEnumerable is defined as Of Out T, and IList is only defined As T.
Now, let's define our own.
Interface ISomething(Of Out T)
ReadOnly Property Value As T
End Interface
Class IntThing
Implements ISomething(Of Integer)
Public ReadOnly Property Value As Integer Implements ISomething(Of Integer).Value
Get
Return 42
End Get
End Property
End Class
Now, do this:
Dim s1 As ISomething(Of Integer) = new IntThing()
Works. Now add this:
Dim s2 As ISomething(Of Object) = s1
Boom, InvalidCastException. Now, the funniest part. Add a second implementation of ISomething:
Class StringThing
Implements ISomething(Of String)
Public ReadOnly Property Value As String Implements ISomething(Of String).Value
Get
Return "foo"
End Get
End Property
End Class
And do:
Dim s1 As ISomething(Of String) = New StringThing()
Dim s2 As ISomething(Of Object) = s1
This, on the other hand, works! So, let's go back to the List example.
Dim ints As IEnumerable(Of Integer) = New List(Of Integer)()
Dim objects As IEnumerable(Of Object) = ints
This will get you an InvalidCastException, too.
So, my conclusion is that covariance not only needs the Out keyword, it additionally only works with non-primitive types. .net seems to handle wrapper classes differently to the JVM.
So, never ignore this warning when it pops up. When it does, things will go wonky in an absolutely illogical way! That means, for what I want to achieve, going with simple Objects instead trying to find an equivalent for ISomething<?> is the way to go.
I only use this internally to read a binary file into a more convenient structure to extract the data I pass out via the API in the end, so using Object does not make things very much worse here.
It's weird, I don't get the warning like you do. But I do get an InvalidCastException if I try to run the code.
To get rid of the error (and hopefully your warning as well), you can make the generic type T on ISomething covariant.
Public Interface ISomething(Of Out T) ' Add the "Out" keyword here to make it covariant
' methods
End Interface
Then you should be able to use your GetSomething function as you had attempted:
Public Function GetSomething(p as ParamType) as ISomething(Of Object)
If p.HasFoo Then Return New ConcreteThing()
If p.HasBar Then Return New OtherConcreteThing()
Throw New InvalidOperationException("p neither has Foo nor Bar")
End Function
Relevant documentation: Covariance and Contravariance in Generics
Covariance
Enables you to use a more specific type than originally specified.
You can assign an instance of IEnumerable<Derived> (IEnumerable(Of Derived) in Visual Basic) to a variable of type IEnumerable<Base>.
And lower in the Defining Variant Generic Interfaces and Delegates section:
A covariant type parameter is marked with the out keyword (Out keyword in Visual Basic, + for the MSIL Assembler).

Display the list of the Selected items in the checkboxlist

Dim list As New List(Of String)
list = chkparameter.Items
.Cast(Of ListItem)
.AsEnumerable()
.Where(Function(x) x.Selected)
.Select(Function(x) x.Value)
The error i am getting is
Unable to cast object of type 'WhereSelectEnumerableIterator2[System.Web.UI.WebControls.ListItem,System.String]' to type 'System.Collections.Generic.List1[System.String]'.
How can i rectify it.
Thanks
If you want to assign the result to a List(Of String) variable then you need a List(Of String) object. You can all ToList on any enumerable list to create a List(Of T).
Also, your AsEnumerable call is pointless because Cast(Of T) already returns an IEnumerable(Of T).
Finally, declaring a variable on one line and then setting its value is so unnecessarily verbose. It's not wrong but it is pointless. In your case, not only are you declaring a variable but you're also creating an object that you never use. Don't create a New object if you don;t actually want a new object, which you don;t because you're getting an object on the very next line.
Dim list As List(Of String) = chkparameter.Items.
Cast(Of ListItem).
Where(Function(x) x.Selected).
Select(Function(x) x.Value).
ToList()
There's also no need to declare the type of the variable because it will be inferred from the initialising expression, i.e. ToList returns a List(Of String) so the type of the variable can be inferred from that. Not everyone likes to use type inference where it's not completely obvious though, so I'll let you off that one. I'd tend to do this though:
Dim list = chkparameter.Items.
Cast(Of ListItem).
Where(Function(x) x.Selected).
Select(Function(x) x.Value).
ToList()
By the way, notice how much easier the code is to read with some sensible formatting? If you're going to use chained function syntax like that, it's a very good idea to put each function on a different line once you get more than two or three.

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.

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

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)