vb.net Source array type cannot be assigned to destination array type on Enum - vb.net

I've had to update a vb.net project from .NetFramework 4 to .NetFramework 4.7.2. In the process the following code is now throwing an error
Dim actuatorModelsArr = DirectCast(retNumberList, Array) Dim
Dim actuatorModels = actuatorModelsArr.Cast(Of ACTUATORMODELS)().ToList()
The error is System.ArrayTypeMismatchException: Source array type cannot be assigned to destination array type.
retNumberList is an Integer array
ACTUATORMODELS is an Enum
in the .netFramework 4 version actuatorModels is a list of the Enum
{System.Collections.Generic.List`1[FisherIECLib.ACTUATORMODELS]}
The list is used later in the module via linq to grab one of the Enums as a return value.
Is there a way around this or a way to create a list of the Enum?
Thanks in advance,
Hank

I think it's interesting that it stopped working. You can replace the code with a Select and cast to make it work
Dim retNumberList = {1, 2, 3, 4}
' either of these will produce a List of your Enum
Dim a = retNumberList.Select(Function(i) DirectCast(i, ACTUATORMODELS)).ToList()
Dim b = retNumberList.Select(Function(i) CType(i, ACTUATORMODELS)).ToList()
As for the original not working:
Dim c = retNumberList.Cast(Of ACTUATORMODELS)().ToList()
The literature suggests this is the equivalent of a (type)obj c# style cast, but both versions of vb.net cast work in the select. I am not sure why.

djv's answer helps fix your problem. Hopefully this answer will explain what's going wrong. If you look at the reference source for Cast(Of T) on an untyped IEnumerable, you'll find that the first thing that happens is the C# equivalent of this:
Dim asTyped = TryCast(source, IEnumerable(Of TResult))
If asTyped IsNot Nothing Then Return asTyped
Surprisingly, this cast will work for Integer() to IEnumerable(Of ACTUATORMODELS). This sets the issue in motion, because when it comes to making a List(Of T) out of the resulting sequence, it turns out that Integer() and ACTUATORMODELS() are actually not interchangeable, but Cast has already treated the sequence as though they are.
Based on some testing, this issue seems to arise out of the interaction of this corner case with a corner case in how the List(Of T) range constructor works and the more general corner case of Integer() vs TEnum().
In the general case of iterating over Integer() as if it were IEnumerable(Of TEnum), it works. You can make a For Each loop over the sequence, the enumeration variable will have TEnum as the type, and you'll see the values as if they were TEnum.
The problem comes in the range constructor for List(Of T), and an optimization there for a source that implements ICollection(Of T). In that case, the ctor will try to use ICollection(Of T).CopyTo to copy the items into the list's internal storage. This is where the error ultimately occurs, because (going back to the implementation of Cast(Of T)) the source is still the Integer() array, and Array.Copy (via Array.CopyTo) is not OK with trying to do that with a destination of TEnum().
I feel like this is a bug somewhere, though I'm not sure if it's in Cast(Of T), the List(Of T) range ctor, or in the array copy handling. I'm also not sure it's something that will get fixed, since it's a corner case that hits what look like significant optimizations and would require a very specialized check to catch.

Related

Iterate over a changing SortedDictionary

Utility code I do not have access to is adding rows to the SortedDictionary(of Integer, RowData) I am trying to iterate over. It is adding new RowData's to the end of the list.
So I think the proper solution is to make a copy of the SortedDictionary, and then loop over it. I don't have to worry about sync problems because the new additions are always after the end.
But how to do this? I can't figure out the syntax, the documentation at MS is, hmmm, "basic", and the API doesn't make any sense whatsoever to me. What I want is a copy of the SortedDictionary making another SortedDictionary. But what I get is some sort of KeyValuePair array that I can't seem to make head nor tails of.
Dim TempR as KeyValuePair(Of Integer, RowData)
pRowDatas.CopyTo(TempR, 0)
That one tells me it can't copy it because one or the other is a 1-dimensional array and the other isn't. So I tried
Dim TempR() as KeyValuePair(Of Integer, RowData)
pRowDatas.CopyTo(TempR, 0)
And then it complained TempR was null. But you can't New it, or at least I can't.
What am I doing wrong here? Is there an easy way to do this?
In your example code, you need to initialize the array to the size of pRowDatas:
Dim TempR(0 To pRowDatas.Count-1) As KeyValuePair(Of Integer, RowData)
pRowDatas.CopyTo(TempR, 0)
There's also a ToArray extension method that you could use:
Dim TempR As KeyValuePair(Of Integer, RowData) = pRowDatas.ToArray()
Neither of these are inherently thread-safe. The proper way to do this would be to implement a lock both here and in the utility code. Since you have no access to the code, it seems your options are limited here, but someone else may have some idea.

List contains duplicate Persons

Please see the code below:
Public Function ExecuteDynamicQuery(ByVal strSQL As String, ByVal list As List(Of clsType), ByVal tyType As clsType) As List(Of clsType) Implements IGenie.ExecuteDynamicQuery
Dim objParameterValues As New clsParameterValues
Dim iConnectionBLL As iConnectionBLL
Dim objCon As DbConnection
Dim objDR As DbDataReader
Dim paramValues() As DbParameter
objParameterValues = New clsParameterValues
iConnectionBLL = New clsConnectionBLL()
objCon = iConnectionBLL.getDatabaseTypeByDescription("Genie2")
Using objCon
paramValues = objParameterValues.getParameterValues
objDR = clsDatabaseHelper.ExecuteReader(objCon, CommandType.Text, strSQL, paramValues)
Do While objDR.Read
Dim tyType2 As clsType = tyType
tyType.PopulateDataReader(objDR)
list.Add(tyType2)
Loop
objDR.Close()
Return list
End Using
End Function
An SQL statement is passed to the function along with clsType (the base type). A list of types is returned e.g. a list of Persons. For example, in this case strSQL = "SELECT * FROM Persons". A list of 500 persons is returned but they are all the same person (the last person added to the list). I realise this is because the list is referncing the same object for each entry. How do I change this?
This is a situation where making the method generic would be useful. For instance:
Public Function MyGenericMethod(Of T As New)() As List(Of T)
Dim results As New List(Of T)()
For i As Integer = 0 To 9
Dim item As New T()
' Populate item ...
results.Add(item)
Next
Return results
End Function
For what it's worth, though, I see people trying do this kind of thing often, and it never sits well with me. I'm always the first one in line to suggest that common code should be encapsulated and not duplicated all over the place, but, I've never been convinced that creating some sort of data access layer that encapsulates the calls to ADO, but doesn't also encapsulate the SQL, is a good idea.
Consider for a moment that ADO, is in-and-of-itself an encapsulation of that part of the data-access layer. Sure, it can take a few more lines of code than you might like to execute a simple SQL command, but that extra complexity is there for a reason. It's necessary in order to support all of the features of the data source. If you try to simplify it, inevitably, you will one day need to use some other feature of the data source, but it won't be supported by your simplified interface. In my opinion, each data access method should use all of the necessary ADO objects directly rather than trying to some how create some common methods to do that. Yes, that does mean that many of your data access methods will be very similar in structure, but I think you'll be happier in the long run.
I've reduced your original code. The following sample is functionally equivalent to what you posted. Without knowing more about your types, it will hard to give you anything more than this, but maybe the reduction will make the code clear enough for you to spot a solution:
Public Function ExecuteDynamicQuery(ByVal sql As String, ByVal list As List(Of clsType), ByVal type As clsType) As List(Of clsType) Implements IGenie.ExecuteDynamicQuery
Dim paramValues() As DbParameter = New clsParameterValues().getParameterValues()
Using conn As DbConnection = iConnectionBLL.getDatabaseTypeByDescription("Genie2"), _
rdr As DbDataReader = clsDatabaseHelper.ExecuteReader(conn, CommandType.Text, sql, paramValues)
While rdr.Read()
type.PopulateDataReader(rdr)
list.Add(type)
End While
Return list
End Using
End Function
There are a few additional bits of advice I can give you:
You must have some way to accept parameter information for your query that is separate from the query itself. The ExecuteReader() method that you call supports this, but you only ever pass it an empty array. Fix this, or you will get hacked.
A implementation that uses Generics (as posted in another answer) would be much simpler and cleaner. The Genie interface you're relying doesn't seem to be adding much value. You'll likely do better starting over with a system that understands generics.
The problem of re-using the same object over and over can be fixed by creating a new object inside the loop. As written, the only way to do that is with a New clsType (and it seems you may have Option Strict Off, such that this could blow up on you at run time), through some messy reflection code, a switch to using generics as suggested in #2, or a by accepting a Func(Of clsType) delegate that can build the new object for you.

VB.Net: Initialising variables with the New constructor

When initialising a list or a queue or a stack or anything similar, which method is preferred?
Dim q as Queue(Of Integer) = New Queue(Of Integer)
or
Dim q as New Queue(Of Integer)
Also, I've started to use New for string and integer variables - is this stupid? Is there any disadvantage to using New rather than just setting the variable to the default setting? E.g.
Dim Num1 As New Integer
Dim Str1 As New String("")
Dim Bool1 As New Boolean
Thank you!
If you'd ask programmers whether they like typing more or less when writing a program then the usual answer is "less!". If you ask them whether they like more or less bugs, the usual answer is "less!" Those are conflicting goals.
The As New syntax has been part of VB.NET for a very long time. It however does come with strings attached, you leave it entirely up to the runtime to figure out whether or not a new object should be created. That does tend to be a bug generator. Sometimes you really do want to create a new object, even though the variable is already assigned. It is also rather ambiguous, in this snippet for example:
For ix As Integer = 0 To 42
Dim q As New Queue(Of Integer)
'' etc...
Next
Question is: do you get one instance of the queue, created in the first pass of the loop or do you get 43 of them? What was actually intended by the programmer? It isn't very clear from the syntax.
There is a 3rd alternative that you overlooked and the one I prefer. Available since VB9 (aka VS2008) called "type inference". Where you don't specify the type of the variable but leave it up to the compiler to figure it out. This option needs to be turned on with Option Infer On, it is turned on by default. It combines the advantages of the abbreviated syntax that As New has and still lets you create the object explicitly in your code with the New statement:
Option Infer On
...
For ix As Integer = 0 To 42
Dim q = New Queue(Of Integer)
'' etc...
Next
Where q is inferred to be of type Queue by the compiler and it is crystal clear that the code generates 43 instances of the queue. The exact equivalent in the C# language is the var keyword.

Don't use ArrayList!

People often tell me not to use ArrayList for making my arrays in VB.NET.
I would like to hear opinions about that, why shouldn't I? What is the best method for creating and manipulating array contents, dimensions etc?
Thanks.
Use generic lists instead. ArrayList is not typed, meaning that you can have a list with strings, numbers, +++. Rather you should use a generic list like this:
Dim list1 As New List(Of String) ' This beeing a list of string
The lists-class also allows you to expand the list on the fly, however, it also enforces typing which helps write cleaner code (you don't have to typecast) and code that is less prone to bugs.
ArrayList is gennerally speaking just a List(Of Object).
ArrayLists are not type checked so you will need to do a lot of boxing/unboxing. Use a .net collection instead that support generics like List.
Because List does not have to unbox your objects it boasts a surprisingly better performance than Arraylist.
ArrayLists are less performant and memory-extensive:
Dim list1 As New ArrayList
For i As Integer = 1 To 100000000
list1.Add(i)
Next
' --> OutOfMemoryException after 13.163 seconds, having added 67.108.864 items
Dim list2 As New List(Of Integer)
For i As Integer = 1 To 100000000
list2.Add(i)
Next
' --> finished after 1.778 seconds, having added all values
Because its not strongly typed. Use a List(Of T) which T is your type.

Iterate through generic list of unknown type at runtime in VB.Net

Does anyone know how to iterate over a generic list if the type of that list isn't known until runtime?
For example, assume obj1 is passed into a function as an Object:
Dim t As Type = obj1.GetType
If t.IsGenericType Then
Dim typeParameters() As Type = t.GetGenericArguments()
Dim typeParam As Type = typeParameters(0)
End If
If obj is passed as a List(Of String) then using the above I can determine that a generic list (t) was passed and that it's of type String (typeParam). I know I am making a big assumption that there is only one generic parameter, but that's fine for this simple example.
What I'd like to know is, based on the above, how do I do something like this:
For Each item As typeParam In obj1
'do something with it here
Next
Or even something as simple as getting obj1.Count().
The method that iterates over your list can specify a generic type:
Public Sub Foo(Of T)(list As List(Of T))
For Each obj As T In list
..do something with obj..
Next
End Sub
So then you can call:
Dim list As New List(Of String)
Foo(Of String)(list)
This method makes the code look a little hairy, at least in VB.NET.
The same thing can be accomplished if you have the objects that are in the list implement a specific interface. That way you can populate the list with any object type as long as they implement the interface, the iteration method would only work on the common values between the object types.
If you know that obj is a Generic List. Then you're in luck.
Generic List implements IList and IEnumerable (both are non-generic). So you could cast to either of those interfaces and then For Each over them.
IList has a count property.
IList also has a Cast method. If you don't know the type to cast to, use object. This will give you an IEnumerable(Of object) that you can then start using Linq against.