Iterate over a changing SortedDictionary - vb.net

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.

Related

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

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.

Use Dictionary by variable name

I have several dictionary types assigned like this
Public aryAAA As New Dictionary(Of Integer, Dictionary(Of String, String))
Public aryBBB As New Dictionary(Of Integer, Dictionary(Of String, String))
Public aryCCC As New Dictionary(Of String, String)
Public aryDDD As New Dictionary(Of String, String)
In a database I stored the names of aryAAA, aryBBB, aryCCC, and aryDDD. In my program if I read a database record and it has aryCCC returned, I then want to be able to use that dictionary.
I was thinking that I would have to assign an object to the aryCCC by iterating through the system.collecton.generic.dictionary and then use that object to retrieve the data stored. Not sure how I would do this or if there is a better way to use the dictionary by a variable returned name?
Thanks for any help on this.
You'll need a Select or If/Else for this. We could do a little better within the type system if all the dictionaries held the same type of object, but since there are differences this is the best we can do:
If dbValue = "aryCCC" Then
'Do stuff with aryCCC
Else If dbValue = "aryAAA" Then
'Do stuff with aryAAA
'etc
End If
Even reflection won't help much here, since those variables aren't members and we'd still have the different types to deal with.
Ultimately, you have a run-time value from the database you want to match up to compile-time variable names, and that never goes well.

vb.net string - replace placeholder tokens efficiently

I'm just looking for some ideas with a little problem. I've a string that's a template for a message, for example:
Dear [[Guest.FirstName]],
We hope your are looking forward to your holiday in [[Booking.ResortName]]
My current system for replacing the spaceholder tokens (e.g. [[Guest.FirstName]]) is very inefficient, there's loops inside loops and it takes for too long.
What I'm looking for is a way that would go through the string until it finds [[something]], then replace that [[something]] with it's real value, then continue on through the string.
Please note to replace [[something]] then I need to have access to the space holder (i.e. i need to know if it's [[Guest.FirstName]] or [[Booking.ResortName]]).
Any suggestions for an efficient way to achieve this would be very much appreciated, I feel it should be fairly straightforward but everything I can think of ends up with loops inside loops again.
Thanks!
Phil.
There are many factors which will affect performance, so it's hard to say, without knowing all the specifics, what method will be most efficient. However, when it comes to coding simplicity, using a RegEx MatchEvaluator would be an excellent option. I'm sure you'll agree that it's much cleaner than whatever you are currently using:
Public Function ReplacePlaceholders(data As String) As String
Dim r As New Regex("\[\[(?<placeholder>.*?)\]\]")
data = r.Replace(data, New MatchEvaluator(AddressOf PlaceHolderReplacementEvaluator))
End Function
Private Function PlaceHolderReplacementEvaluator(match As Match) As String
Dim name As String = match.Groups("placeholder").Value
Return LookUpValueByPlaceholderName(name) ' Replace with value lookup logic here
End Function
If the total number of placeholders in the data is going to be rather small, and the list of possible placeholders is small, it's probably best to just have a list of them with their values and replace them like this:
Public Function ReplacePlaceholders(data As String) As String
Dim placeHolders As Dictionary(Of String, String) = LoadPlaceHolders()
For Each i As KeyValuePair(Of String, String) In placeHolders
data = data.Replace(i.Key, i.Value)
Next
Return data
End Function
Private Function LoadPlaceHolders() As Dictionary(Of String, String)
Dim placeholders As New Dictionary(Of String, String)
' Load data here
Return placeholders
End Function
If you really want the most efficient solution, though, going character by character and appending, as you go, to a StringBuilder or an output Stream, is going to be your best option. It's not going to be pretty, but if you post what you have to CodeReview, there may be some people who could find ways of making it slightly less ugly :)

vba: Dictionary passed to function acting as "ByRef" when forced as "ByVal"

I have a dictionary dictA that contains {AA,1},{AB,2},{AC,3} and an adjustment dictionary dictAdj containing {AB,-4}.
I pass these two into a function call it funcReconcile(ByVal Arg1 as Dictionary, ByVal Arg2 as Dictionary) as Dictionary.
The function is going to loop through the contents of dictAdj to see if they exist in dictA.
The function will then adjust dictA (for example) to show {AA,1},{AB,-2},{AC,3} in the output dictionary.
For some reason, when the function returns the dictionary, my original dictA is also effected, causing it to display {AA,1},{AB,-2},{AC,3} and not {AA,1},{AB,4},{AC,3} as it should.
I know there are issues in VBA with passing ByRef and ByVal for objects, but this is the first time really hitting my head against the wall with something like this.
Any pointers? (no pun intended)
You would have to build a copy method, make a complete copy of the dictionary, and then make modifications on the copy.
Assigning to a new variable just assigns the original reference to the new variable -- which points back to the original data.
Here's some fragmentary code from Copy from one dictionary object into another?:
a=dictionary1.items
b=dictionary1.keys
for i=0 to ubound(a)
dictionary2.add(b(i), a(i))
next
This post is really old, it may not help the original poster. I came across this while researching the same. My solution (I believe) is better. It uses linq instead of a loop which saves resources. My code is in vb as that's what my project is using, but you can use a code converter to change the function.
Simple 1 line solution. 2 separate dictionaries that are true copies (separate identities):
Dim dictionary2 As Dictionary(Of Integer, String) = dictionary1.ToDictionary(Function(k) k.Key, Function(k) k.Value.ToString())

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.