Nested Dictionary System.NullReferenceException in VB.net [duplicate] - vb.net

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 5 years ago.
I have scoured the internet looking for a solution to this problem but the null exception remains. Basically I am getting System.NullReferenceException error when attempting to add values to a nested dictionary.
Here is my code.
Public Dict1 as New Dictionary(Of Integer, Dictionary(Of String, String))() 'Tried with and without()
Sub CreateDict(Length As Integer)
Dim key As Integer
Dim CmdName As String
Dim CmdValue As String
key = 0
CmdName = "UnusedCmd"
CmdValue = "NotAFolder,NotAFolder,NotAFolder,NotAFolder,NotAFolder,Read"
While key <= Length
Dim CmdDict As New Dictionary(Of String, String)
CmdDict.Add("Name", CmdName) 'Method 1 of Adding
CmdDict.Add("Command", CmdValue) 'Method 1 of Adding
'CmdDict("Name") = CmdName 'Method 2 of Adding Both seem to work as evidenced by msgbox
'CmdDict("Command") = CmdValue 'Method 2 of Adding Both seem to work as evidenced by msgbox
MsgBox(CmdDict("Name")) ' Returns UnusedCmd
MsgBox(CmdDict("Command"))' Returns NotAFolder,NotAFolder,NotAFolder,NotAFolder,NotAFolder,Read
MsgBox(key) 'Returns 1
Dict1.Add(key, CmdDict)
key = key + 1
CmdDict = Nothing 'Error Occurs whether or not this is commented out
End While
End Sub
Note this code was adapted from my original project which was in VBA for outlook 2016. I am trying to readapt this code to be an outlook addin.
I have tried replacing the "As" terms with "=" and I have also tried
Dim CmdDict As Dictionary(Of String, String) = New Dictionary(Of String, String)
For both dictionaries but this didn't work. I have tried everything that I can think of / find please help.
Could it have something to do with it being a public dictionary? I saw someone else using a public dicitonary and they're solution was to add new which I new I had to do otherwise no object would be created.
EDIT:
I just tried a regular dictionary as a public variable and it failed as well. So it appears to have nothing to do with being Nested. I will continue looking into the public object issue.
EDIT 2:
Just Tried adding Dict1 to Public Class Still not working.
Public Class GlobalVariables
Public Shared Dict1 As Dictionary(Of Integer, Dictionary(Of String, String)) = New Dictionary(Of Integer, Dictionary(Of String, String))
End Class
After doing this Dict1 was updated to GlobalVariables.Dict1
EDIT 3:
This code is within a VSTO Addin that I am creating based on macros that I wrote in Outlook VBA. I am using Visual Studio Community 2015. The location of the code is as follows
Edit 4:
Duplicate? I agree I thought that this was a duplicate answer as well but I couldn't anywhere else where the answer was to move the Create Object from Public to within a Sub for a public variable. But the rest of the question is very similar to others agreed.

Try it this way.
Public Dict1 as Dictionary(Of Integer, Dictionary(Of String, String))
Sub CreateDict(Length As Integer)
Dict1 = New Dictionary(Of Integer, Dictionary(Of String, String))
<Rest of your code>
End Sub

Related

Convert Dictionary(Of String, Object) to Dictionary(Of String, String)

When I try to pass a Dictionary(Of String, Object) to a function parameter that wants a Dictionary(Of String, String) I get the following error:
Unable to cast object of type 'System.Collections.Generic.Dictionary'2[System.String,System.Object]' to type 'System.Collections.Generic.Dictionary'2[System.String,System.String]'.
All of the Object values in the dictionary are strings but the dictionary was declared as String/Object. I would have thought that the system would be able to convert this but since it isn't I need to do it myself.
I looked at the .ToDictionary() prototype method but all of the examples show a list being converted to a dictionary.
I found this question which has an accepted answer for what I want but it's written in C# and I can't figure out the conversion to VB.Net.
Edit 1
Offending code. Obviously boiled down or else I would just simply declare dict1 as string/string in my actual code.
Dim dict1 As New Dictionary(Of String, Object) From {{"key1","value1"}}
SomeFunctionThatExpectsParamToBeDictOfStringString(dict1)
Edit 2
I tried:
SomeFunctionThatExpectsParamToBeDictOfStringString(dict1.ToDictionary(Function(k) k.Key, Function(v) v.Value.ToString()))
but got:
System.MissingMemberException: Public member 'ToDictionary' on type 'Dictionary(Of String,Object)' not found.
This could be the VB.NET version of the C# code you have linked
Dim dic1 As Dictionary(Of String, Object) = New Dictionary(Of String, Object)
dic1.Add("A", "B")
Dim dic2 As Dictionary(Of String, String) = dic1.ToDictionary(Function(k) k.Key,
Function(v) v.Value.ToString())

I am unsure about how to remove cases from the "select case" statement in visual basic after a certain case has been selected more than 3 times

I've a word guessing game in development. It works so that when the play button is clicked, a random number between 1 and 20 will be generated. This new randomly generated number will then go into the select case statement as shown in the code below:
Dim RND As New Random
Dim rndNumber As String
rndNumber = RND.Next(1, 20)
Dim RndWord as string
Dim RndHint as string
select case(rndnumber)
case 1
RndWord = "hockey"
RndHint = "A ball game played with curved, wooden sticks"
Case 2
RndWord = "dinghy"
RndHint = "This is a small boat usually made out of rubber"
These are just 2 out of 20 similar cases.
The selected case contains a word and a hint that will be displayed upon that case being selected. My problem is, how do I remove a case after it has been selected three times; removing the word and the hint from the program completely so that they won't appear again. I've looked into different types of arrays; however, after 2 hours of research and many attempts at using them they don't seem to fit this purpose.
Firstly remove your case statement, it makes is less manageable. What if tomorrow you decide to add 5 more questions? You will end up changing the code. You can keep all the data externally and have them read into a dictionary object.
Define these at class level:
Dim questions As Dictionary(Of Integer, KeyValuePair(Of String, String)) = New Dictionary(Of Integer, KeyValuePair(Of String, String))()
Dim questionAppearedCount As Dictionary(Of Integer, Integer) = New Dictionary(Of Integer, Integer)()
Create new Sub with this:
Public Sub FillQuestions()
questions.Add(1, New KeyValuePair(Of String, String)("hockey", "A ball game played with curved, wooden sticks"))
questions.Add(2, New KeyValuePair(Of String, String)("dinghy", "This is a small boat usually made out of rubber"))
'Also, you may use File.ReadAllLines() to fill from a file.
End Sub
Finally, BtnGenerate_Click
Dim rnd As Random = New Random()
Dim rndNumber As Integer = rnd.[Next](1, 20)
If questionAppearedCount.ContainsKey(rndNumber) Then
questionAppearedCount(rndNumber) = questionAppearedCount(rndNumber) + 1
Else
questionAppearedCount.Add(rndNumber, 1)
End If
If questionAppearedCount(rndNumber) > 3 Then
'do not show question, instead get Next random
Else
return question(rndNumber)
End If
Here is the code, so I keep questions in dictionary, show them using dictionary.
In the above code, question can be read from external file using File.ReadAllLines()
When question is shown, add to the count and if count is > 3, don't show.
Make an array to count each case.
If the count is 3 you loop.
If less you increase the counter in the array and exit the loop so it ll use a case statement.
Be carreful in this case if all counters reach 3 the program wil loop infinitively.
Else an array for all the cases data.
When 3 is reached the datas are moved at the end of the array and the max index is decreased by one. At beginning its 20... next 19.. etc...
I recommend this code.
My sample also has a list of questions.
A very important differences though:
It does the filtering of the data BEFORE the random call. So, it's only searching for the questions that are valid. In certain cases, repeated random calls very close together might produce the same result, so this verifies you only do it once.
Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.Linq
Public Module Module1
Dim questionAppearedCount As Dictionary(Of Integer, Integer)
Public Sub Main()
Dim questions As Dictionary(Of Integer, KeyValuePair(Of String, String)) = New Dictionary(Of Integer, KeyValuePair(Of String, String))()
questions.Add(1, New KeyValuePair(Of String, String)("hockey", "A ball game played with curved, wooden sticks"))
questions.Add(2, New KeyValuePair(Of String, String)("dinghy", "This is a small boat usually made out of rubber"))
questionAppearedCount = New Dictionary(Of Integer, Integer)()
SelectQuestion(1)
SelectQuestion(1)
SelectQuestion(1)
' Get the Filtered List
Dim filteredQuestions() as KeyValuePair(Of Integer, KeyValuePair(Of String, String)) = questions.Where(Function(ByVal item as KeyValuePair(Of Integer, KeyValuePair(Of String, String))) Not questionAppearedCount.ContainsKey(item.Key) OrElse questionAppearedCount(item.Key) < 3).ToArray()
Dim rand As New Random
Console.WriteLine(filteredQuestions(rand.Next(0, filteredQuestions.Count - 1)).Value)
End Sub
Public Sub SelectQuestion(ByVal questionNumber as Integer)
' Add Question
If questionAppearedCount.ContainsKey(questionNumber) Then
questionAppearedCount(questionNumber) += 1
Else
questionAppearedCount(questionNumber) = 1
End If
End Sub
End Module
And here's some vb.net helper code that you can use to REFACTOR your case statements automatically. Just paste your code into it and it will return the equivalent code to the console. You'll replace your code with this code.
Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.Linq
Public Module Module1
Dim questionAppearedCount As Dictionary(Of Integer, Integer)
Public Sub Main()
'Dim questions as New Dictionary(Of Integer, KeyValuePair(Of String, String))
For rndnumber as Integer = 1 to 20
Dim RndWord as String = ""
Dim RndHint as String = ""
select case(rndnumber)
case 1
RndWord = "hockey"
RndHint = "A ball game played with curved, wooden sticks"
Case 2
RndWord = "dinghy"
RndHint = "This is a small boat usually made out of rubber"
end SElect
If Not String.IsNullOrEmpty(rndword) Then
'questions.Add(rndnumber, new KeyValuePair(Of String, String)(rndword, rndhint))
Console.WriteLine("questions.Add(" & rndnumber & ", new KeyValuePair(Of String, String)(""" & rndword.Replace("""", """""") & """,""" & RndHint.Replace("""", """""") & """))")
End If
Next
End Sub
End Module

Contents of List(Of String) are not saved

I am attempting to parse paragraphs such as the following...
Group 1. Does this or does that. Or Sometimes this. Or that.
Group 2. I do lots of things. But not this. Or that.
Group 3. I do this. I do that. Sometimes this. Sometimes that.
The "Group 1-3" are the org Names, and each following sentence separated by a period is a function.
Code:
Public Sub parseParagraphs(paragraphList As List(Of String))
Dim listOfOrgs As New List(Of EAB_Org)
Dim listOfFuntions As New List(Of String)
Dim orgName As String
For Each item In paragraphList
listOfFuntions.Clear()
Dim words As String() = item.Split(New Char() {"."c}) 'Splits on periods
orgName = words(0) 'Sets the orgName
For index As Integer = 1 To words.Count - 1 'rest of items in list are functions performed
listOfFuntions.Add(words(index))
Next
Dim anOrg As New EAB_Org(orgName, listOfFuntions)
listOfOrgs.Add(anOrg)
Next
End Sub
EAB Class:
Public Class EAB_Org
Dim orgName As String
Dim listOfTasks As List(Of String)
Public Sub New(theOrgName As String, theListOfTasks As List(Of String))
orgName = theOrgName
listOfTasks = theListOfTasks
End Sub
Public Function getOrgName()
Return orgName
End Function
Public Function getListOfTasks()
Return listOfTasks
End Function
End Class
For some reason, when I print out the contents of listOfOrgs, all the org names are correct, but the functions are all of the same and always the last set of functions read in.
Code I use to print:
Public Sub writeExcel(listOfOrgs As List(Of EAB_Org))
For Each anItem In listOfOrgs
Console.WriteLine(anItem.getOrgName)
For Each anotherItem In anItem.getListOfTasks
Console.WriteLine(anotherItem)
Next
Next
End Sub
Output Looks Like:
Group 1
I do this. I do that. Sometimes this. Sometimes that.
Group 2
I do this. I do that. Sometimes this. Sometimes that.
Group 3
I do this. I do that. Sometimes this. Sometimes that.
The problem is that in the constructor for EAB_Org, theListOfTasks is just a pointer to listOfFuntions (which you keep modifying) in the parseParagraphs Sub. In the constructor, you will need to create a new List(Of String) and copy the values from theListOfTasks into it.
Change the constructor to the following:
Public Sub New(theOrgName As String, theListOfTasks As List(Of String))
orgName = theOrgName
listOfTasks = New List(Of String)
For Each item As String In theListOfTasks
listOfTasks.Add(item)
Next
End Sub

How to sort SortedDictionary by values in .NET 2.0?

I have a SortedDictionary:
Dim myFilterItems As SortedDictionary(Of String, FilterItem)
myFilterItems = New SortedDictionary(Of String, FilterItem)(StringComparer.CurrentCultureIgnoreCase)
The FilterItem class is defined like this:
Private Class FilterItem
Public ValueToSort As Object
Public IsChecked As Boolean
Public IsAbsent As Boolean = False
End Class
I need to enumerate my SortedDictionary sorted by the FilterItem.ValueToSort property. With LINQ, it's easy to do - we get the corresponding IEnumerable and then use For Each:
Dim mySortedValueList As IEnumerable(Of KeyValuePair(Of String, FilterItem))
mySortedValueList = From entry In myFilterItems Order By entry.Value.ValueToSort Ascending
For Each entry As KeyValuePair(Of String, FilterItem) In mySortedValueList
' ...
Next
How to do that effectively in .NET 2.0?
I rewrote my code using Lists as Jon Skeet suggested. As I can see from my tests, I have the same performance like with the LINQ query - or even 5-10% gain. The new version of code looks like this:
Dim mySortedValueList As New List(Of KeyValuePair(Of String, FilterItem))(myFilterItems)
If iArr <> iGAFMValueType.Text Then
mySortedValueList.Sort(AddressOf CompareFilterItemsByValuesToSort)
End If
Private Shared Function CompareFilterItemsByValuesToSort(ByVal itemX As KeyValuePair(Of String, FilterItem), ByVal itemY As KeyValuePair(Of String, FilterItem)) As Integer
Dim myValueX As IComparable = TryCast(itemX.Value.ValueToSort, IComparable)
Dim myValueY As IComparable = TryCast(itemY.Value.ValueToSort, IComparable)
If (myValueX Is Nothing) Then
If (myValueY Is Nothing) Then
Return 0
End If
Return -1
End If
If (myValueY Is Nothing) Then
Return 1
End If
Dim myTypeX As Type = myValueX.GetType()
Dim myTypeY As Type = myValueY.GetType()
If (myTypeX <> myTypeY) Then
Return String.CompareOrdinal(myTypeX.Name, myTypeY.Name)
End If
Return myValueX.CompareTo(myValueY)
End Function
However, while working on this comparison algorithm, I found that I can't compare values of some numeric types - though they can be compared (for instance, SByte and Double values). BTW, the original LINQ query also fails in this case. But this is another story that continues in this question:
How to compare two numeric values (SByte, Double) stored as Objects in .NET 2.0?

Writing Entries in a VB Dictionary into a Text File

I'm working on VB in college and am running into a snag with one of my assignments. Can someone help? I'm aiming to try to take the following dictionary code:
Public Class Inventory
Public ItemInventory As New Dictionary(Of String, Item)
Public Function iItem(ByVal key As String) As Item
Return ItemInventory(key)
End Function
Public Sub addItem(ByVal item As String, ByVal Desc As String, ByVal DRate As Double, ByVal WRate As Double, _
ByVal MRate As Double, ByVal Quantity As Integer)
With ItemInventory
.Add(item, New Item(item, Desc, DRate, WRate, MRate, Quantity))
End With
End Sub
Public Sub removeItem(ByVal item As String)
With ItemInventory
.Remove(item)
End With
End Sub
Public Function returnKeys() As String()
Dim Keys() As String
With ItemInventory
Keys = .Keys.ToList.ToArray
End With
Return Keys
End Function
End Class
Not pretty, I know, but it gets the job done, that's all I aim to do. Now a bit of this also has to do with displaying a dictionary item in the program, which I'm also having issues with, however, I'd like to take this one step at a time, so we'll get to that later, if possible.
As per writing, this is my current code for reading and writing:
Imports System.IO
Public Class InventoryFile
Public Sub RFile(ByVal FPath As String, ByRef dInventory As Inventory)
Dim infile As StreamReader = File.OpenText(FPath)
Dim entireLine As String = infile.ReadLine()
Dim fields() As String = entireLine.Split(","c)
While infile.EndOfStream
Dim dItem As New Item
dItem.ID = fields(0)
dItem.Description = fields(1)
dItem.Daily = fields(2)
dItem.Weekly = fields(3)
dItem.Monthly = fields(4)
dItem.Quantity = fields(5)
'AddItem
dInventory.addItem(dItem.ID, dItem.Description, dItem.Daily, dItem.Weekly, _
dItem.Monthly, dItem.Quantity)
End While
End Sub
Public Sub WFile(ByVal FPath As String, ByRef dInventory As Inventory)
Dim outfile As StreamWriter = File.CreateText(FPath)
For Each Item As KeyValuePair(Of String, Item) In dInventory.ItemInventory
Next
End Sub
End Class
I hope that posted right. Now, reading in, as far as I understand, works just fine, in terms of a file going into a dictionary, however 'WFile', my StreamWriter, is what's got me stumped. Can someone help me with that? Likewise, its supposed to close and write to the file upon closing, and my only code for the close button is the Me.Close() command. How would I write a trigger to make the program write to the file? Know that the main form code, and my 'InventoryFile' are both separate classes, so this has to be done by referencing the other classes in question
Try this to write each dictionary key/value pair on a single line in the file:
Dim fs As FileStream
' Open the stream and write to it.
fs = File.OpenWrite(FPath)
For Each Item As KeyValuePair(Of String, Item) In dInventory.ItemInventory
fs.Write("{0}:{1}", Item.Key, Item.Value)
Next
UPDATE:
Since Item is both the variable used in the loop and the name of a class, change it to another name like singleItem and then pull out the other pieces of information from the Value portion of the key/value pair, because the Value is actually an Item class object, like this:
Dim fs As FileStream
' Open the stream and write to it.
fs = File.OpenWrite(FPath)
For Each singleItem As KeyValuePair(Of String, Item) In dInventory.ItemInventory
fs.Write("{0}:{1}:{2}:{3}:{4}:{5}:{6}", singleItem.Key, singleItem.Value.ID, singleItem.Value.Description, singleItem.Value.Daily, singleItem.Value.Weekly, singleItem.Value.Monthly, singleItem.Value.Quantity)
Next
In order to create the format your RFile procedure can read you need something like this:
For Each kvp As KeyValuePair(Of String, Item) In dInventory.ItemInventory
' using Karl's compact approach, but add commas
' since your Read expects them
outfile.Write("{0},{1},{2},{3},{4}...", kvp.Key, kvp.Value.ID, _
kvp.Value.Description, ... kvp.Value.Quantity.Tostring)
' the Value part of the fvp is another class, right? `Value.XXX` should drill
' into it to get to the members
Next
outfile.flush
outfile.close ' look into 'Using...'
Thats NOT how I would do it, look into serialization for a less fragile way to read/write the data. It is basically meant for just this sort of thing: save class data for later, and it is not that hard to use.
as for hooking it up, the button click would just call Inventory.WFile