Odd Result with a dictionary of Dictionaries, why? - vb.net

I have need in a module for a dictionary of dictionaries but for some reason it is not producing the results That I was expecting. I have made up an example that illustrates the problem , shown below.
Dim mydict As New Dictionary(Of Int32, Dictionary(Of Int32, Decimal))
Dim outerkey As Integer = 2
Dim innerkey As Integer = 7
Dim innerDictionary As Dictionary(Of Int32, Decimal) = Nothing
Dim innerkeyvalue As Decimal = CDec(12.3)
If Not mydict.TryGetValue(outerkey, innerDictionary) Then
' ' So we need to create one
innerDictionary = New Dictionary(Of Int32, Decimal)
mydict.Add(outerkey, innerDictionary)
End If
If Not innerDictionary.TryGetValue(innerkey, innerkeyvalue) Then
' So we need to create it
innerDictionary.Add(innerkey, innerkeyvalue)
Else
Dim retrievedvalue As Decimal = innerDictionary.Item(innerkey)
innerkeyvalue += retrievedvalue
innerDictionary.Add(innerkey, innerkeyvalue)
End If
If I place a breakpoint on the 'If Not innerDictionary.TryGetValue(innerkey, innerkeyvalue) Then
' line the value of innerkey is 7 and the value of innerkeyvalue is 12.3, which is exactly as I would expect. Those values aren't yet in the inner dictionary so I would then expect the code to go to the next line (which it does) to add those values to the inner dictionary.
What happens though is that the value of the innerkey remains as it should (7) but the value of innervalue changes from 12.3 to 0.
I'm obviously doing something stupid, or I have failed to understandstand a fundamental principle of dictionaries within dictionaries. Either way I'd be grateful if someone could explain why I'm seeing what I'm seeing.

if you read the MSDN doc
if the key is not found, then the value parameter gets the appropriate default value for the type TValue; for example, 0 (zero) for integer types, false for Boolean types, and null for reference types.
default value for decimal is 0
you would need another variable for the second parameter of the trygetvalue

Related

Adding Values to Nested Dictionary

Currently I'm trying to add values into a nested dictionary using VB. I have it working for a flat dictionary, but can't quite get my head around the syntax for doing it nested.
What I have so far is, I have commented the lines I'm having trouble with:
Public Shared Dim dictionary AS New System.Collections.Generic.Dictionary(Of String, System.Collections.Generic.Dictionary(Of String, Integer))
Function addValue(ByVal code AS String, ByVal cust AS String,ByVal value AS Integer)
Dim innerDict AS New System.Collections.Generic.Dictionary(Of String, Integer)
innerDict.Add(cust,value);
IF dictionary.ContainsKey(code) Then
IF dictionary.Item(code).ContainsKey(cust) Then 'Can I access the Customer key in this way?
dictionary.Item(code).Item 'Here I need to update the value held by customer to the old value + new value.
Else
dictionary(code).Add(cust,value) 'Is this syntax correct?
End If
Else
dictionary.Add(code,innerDict)
End If
End Function
What I want to happen is to have a dictionary structured as follow:
Code1:
Customer1: 12
Customer2: 13
Code 2:
Customer1: 12
Customer2: 13
Here is the function that will do what you want.
The first thing it does, is checks whether an entry exists for code in dictionary. If none exists, it adds one whose value is an empty dictionary that will receive the cust-value pairs.
Currently the function does not return any value. If no value is to be returned, you should use a Sub.
Function addValue(ByVal code As String, ByVal cust As String, ByVal value As Integer)
' If no entry for code, create one.
If Not dictionary.ContainsKey(code) Then
dictionary.Add(code, New System.Collections.Generic.Dictionary(Of String, Integer))
End If
' Add cust, value to entry at code.
dictionary(code).Add(cust, value)
End Function
' Returns sum the customer's values.
Function SumCustomerValues(customer As String) As Integer
Dim sum As Integer = 0
For Each d As KeyValuePair(Of String, System.Collections.Generic.Dictionary(Of String, Integer)) In dictionary
If d.Value.ContainsKey(customer) Then
sum += d.Value(customer)
End If
Next
Return sum
End Function

Reference and Value types

I have read that String was a "reference type", unlike integers. MS website
I tried to test its behavior.
Sub Main()
Dim s As New TheTest
s.TheString = "42"
Dim z As String = s.GimmeTheString
z = z & "000"
Dim E As String = s.TheString
s.GimmeByref(z)
end sub
Class TheTest
Public TheString As String
Public Function GimmeTheString() As String
Return TheString
End Function
Public Sub GimmeByref(s As String)
s = TheString
End Sub
End Class
So I expected :
z is same reference as TheString, thus TheString would be set to "42000"
Then Z is modified by reference by GimmeByref thus Z is set to whatever TheString is
Actual result:
Z="42000"
E="42"
TheString="42"
What point am I missing?
I also tried adding "ByRef" in GimmeByRef : yes obviously the GimmeByRef does work as expected, but it also does if I put everything as Integer, which are said to be "Value type".
Is there any actual difference between those types?
The confusion comes about because regardless of type, argument passing in VB is pass by value by default.
If you want to pass an argument by reference, you need to specify the argument type as ByRef:
Public Sub GimmeByref(ByRef s As String)
You also need to understand the difference between mutating a value and re-assigning a variable. Doing s = TheString inside the method doesn’t mutate the value of the string, it reassigns s. This can obviously be done regardless of whether a type is a value or reference type.
The difference between value and reference types comes to bear when modifying the value itself, not a variable:
obj.ModifyMe()
Strings in .NET are immutable and thus don’t possess any such methods (same as integers). However, List(Of String), for instance, is a mutable reference type. So if you modify an argument of type List(Of String), even if it is passed by value, then the object itself is modified beyond the scope of the method.
Strings are immutable, every time you do a change it creates a new "reference" like if New was called.
A String object is called immutable (read-only), because its value
cannot be modified after it has been created. Methods that appear to
modify a String object actually return a new String object that
contains the modification. Ref
Your code basically does something like this:
Sub Main()
Dim a, b As String
a = "12"
b = a
a = a & "13"
Console.WriteLine(a) ' 1213
Console.WriteLine(b) ' 12
Console.ReadLine()
End Sub

How to use reflection to get keys from Microsoft.VisualBasic.Collection

I know that it is not possible to get keys (How to get the Key and Value from a Collection VB.Net) and it is better to use other classes. However I am debugging 10 years old code and I cannot change it.
In the Watch Window I see:
So it seems to be possible to deal the collection as "List(of KeyValuePair)". Can I do it in code or is it only an internal translation.
I basically need to list all KEYs in a Collection.
VisualBasic.Collection has an undocumented private method InternalItemsList which allows reading keys besides values. The type of InternalItemsList is Microsoft.VisualBasic.Collection.FastList which does not seem to be Enumerable but has a method Item which returns items with a zero based index. The type of those items is Microsoft.VisualBasic.Collection.Node. An item has two private properties m_Value and m_Key. And here we are.
Following code illustrates how to convert VisualBasic.Collection to List(Of KeyValuePair.
Dim flg As BindingFlags = BindingFlags.Instance Or BindingFlags.NonPublic
Dim InternalList As Object = col.GetType.GetMethod("InternalItemsList", flg).Invoke(col, Nothing)
Dim res As New List(Of KeyValuePair(Of String, Object))
For qq As Integer = 0 To col.Count - 1
Dim Item As Object = InternalList.GetType.GetProperty("Item", flg).GetValue(InternalList, {qq})
Dim Key As String = Item.GetType.GetField("m_Key", flg).GetValue(Item)
Dim Value As Object = Item.GetType.GetField("m_Value", flg).GetValue(Item)
res.Add(New KeyValuePair(Of String, Object)(Key, Value))
Next
I know that using undocumented functions is not safe but it is for internal use only and only solution I have managed to find.

ByRef in VB.NET

I have written the following code in VB.NET:
Dim obj As Object
obj = "00"
Test(obj)
MsgBox(obj)
Private Sub Test(ByRef num As Integer)
End Sub
Private Sub Test(ByVal num As Integer)
End Sub
When the value "00" is passed "ByRef" in the method "Test" it converts to 0. But if the value "00" is passed "ByVal" it keeps the same value as "00". How the passed value is being converted only depending of the signature?
In VB6 although the default passing type is "ByRef", still the same code keeps the same value("00")
Could anybody explain the reason behind this contradictory behaviour in VB6 and VB.NET?
The way you are doing it, the ByRef changes the type of the object from string to integer. By default, integer do not have trailling "0" when covnerted to strings.
This example below might help you understand what is hapenning.
Sub Main()
Dim o1 As Object = "00"
Dim o2 As Object = "00"
Console.WriteLine(o1.GetType().ToString())
Test1(o1)
Console.WriteLine(o1.GetType().ToString())
Console.WriteLine(o2.GetType().ToString())
Test2(o2)
Console.WriteLine(o2.GetType().ToString())
Console.ReadLine()
End Sub
Sub Test1(ByVal num As Integer)
End Sub
Sub Test2(ByRef num As Integer)
End Sub
Output
System.String
System.String
System.String
System.Int32
I suggest you always turn Option Strict On, this will remove a lot of confusion.
The object is of type System.String. It cannot be passed ByRef to a method, it is of the wrong type. So the compiler has to work around it and rewrites the code:
Dim obj As Object
obj = "00"
Dim $temp As Integer
$temp = CInt(obj)
Test($temp)
obj = $temp '' <=== Here
MsgBox(obj)
The indicated statement is the one that changes the object from a string to an integer. Which, converted again to a string by the MsgBox() call, produces "0" instead of "00".
Notable is that C# does not permit this and generate a compile error. This rewriting trick is rather nasty, if the method itself changes the original object then you'll have a very hard time guessing what is going on since that doesn't change the passed argument value.
ByRef means that value passes by reference and in function will be used the same value what has been sent.
ByVal means that value passes by value (function creates a copy of passed value) and you use only copy of value.

IndexOf a ComboBox just will not work for me

VB2010. I am trying to populate a ComboBox with the contents of an Enumeration of units. I have managed to do this with a Dictionary. Something like
Dim dUnits As New Dictionary(Of String, Integer)
Dim da As String
For Each enumValue As eUnits In System.Enum.GetValues(GetType(eUnits))
da = ConvertEnumToCommonName 'gets unique name for an enumeration
dUnits.Add(da, enumValue)
Next
cbo.DisplayMember = "Key" 'display the the common name
cbo.ValueMember = "Value" 'use the enumeration as the value
cbo.DataSource = New BindingSource(dUnits, Nothing)
When I load up my form that works well. Now the user can choose to select a default unit to display. So then I try
Dim defUnits As eUnits = eUnits.Feet
Dim idx As Integer = cbo.Items.IndexOf(defUnits) 'doesnt work, returns a -1
cbo.SelectedIndex = idx
I have been doing some research for some time and am fairly sure that this has to do with the ComboBox storing Values as a string and in reality I'm searching for an enumeration which is an integer. Don't know if I have that right or not. Anyway, I just cant seem to get the default item selected. Is there another approach I can try?
First of all you have a collection of integers and you're searching for the enum value. For that, try one of the following:
Store the enum value in the dictionary instead of a string:
Dim dUnits As New Dictionary(Of String, eUnits)
Keep the integers in the Dictionary but use the integer value of the enum when searching the ComboBox:
Dim idx As Integer = cbo.Items.IndexOf(CInt(defUnits))
But this is not going to work yet. You are data-bound to a Dictionary, which means the items in cbo.Items are not of the enum type, but of the type of the elements in the Dictionary (KeyValuePair(Of String, eUnits) assuming #1 above).
The easiest solution is just to set the SelectedValue property of the combo box instead of the SelectedIndex. Assuming you used option #1 above, this would be:
cbo.SelectedValue = defUnits
If you used option #2 instead, you'll have to convert this to an integer first:
cbo.SelectedValue = CInt(defUnits)