I'm in the process of creating a tile based game. This game requires a method to load a text file and write the numbers between the delimiter "-", to a multidimensional array. However, an error message "object not set to an instance occurs.
'Load map
Public Sub LoadMap(ByVal URI As String)
Using reader As New System.IO.StreamReader(URI)
For x As Integer = 0 To 13
Dim line = reader.ReadLine()
Dim tokens() As String = line.Split("-")
'adds values to multidimensional array
For y As Integer = 0 To 16
Me.map(x, y) = Integer.Parse(tokens(y))
Next y
Next x
End Using
End Sub
Example map - numbers represent image id's
2-2-2-0-0-0-0-0-0-0-0-3-3-5-5-5-5
2-2-2-0-0-0-0-0-0-0-0-3-3-5-5-5-5
2-2-2-0-0-0-0-0-0-0-0-3-3-2-2-2-5
2-2-2-0-0-0-0-0-0-0-0-3-3-2-2-2-5
2-2-2-0-0-0-0-0-0-0-0-3-3-2-2-2-5
0-0-0-0-0-0-0-0-0-0-0-3-3-2-2-2-5
3-3-3-3-3-3-3-3-3-3-3-3-3-3-3-3-3
4-4-4-4-4-3-4-4-4-4-4-4-4-2-2-2-2
4-4-4-4-4-3-4-4-4-4-4-4-4-2-2-2-2
4-4-4-4-4-3-4-4-4-4-4-4-4-2-2-2-2
4-4-4-4-4-3-4-4-4-4-4-4-4-2-2-2-2
4-4-4-4-4-3-4-4-4-4-4-4-4-2-2-2-2
4-4-4-4-4-3-4-4-4-4-4-4-4-2-2-2-2
4-4-4-4-4-3-4-4-4-4-4-4-4-2-2-2-2
I can't seem to establish the problem. Thanks in advance...
Always use Option Strict On! (Your code shouldn’t even compile, you need to parse the input integers, or your map stores strings, which is equally as bad since you logically store numbers.)
Name your variables properly.
Omit vacuous comments. Only comment things that aren’t obvious from the code.
Don’t hard-code magic numbers (what’s 11? 8?)
Don’t declare variables before initialising. Declare on first use.
The outer loop in your code makes no sense.
Don’t close streams manually, always use a Using block to ensure the program still works in the face of exceptions.
Initialise map.
Which leaves us with:
Public Sub LoadMap(ByVal URI As String)
Const MapHeight As Integer = 12
Const MapWidth As Integer = 9
Me.map = New Integer(MapHeight, MapWidth) { }
Using reader As New System.IO.StreamReader(URI)
For x As Integer = 0 To MapHeight - 1
Dim line = reader.ReadLine()
Dim tokens() As String = line.Split("-")
For y As Integer = 0 To MapWidth - 1
Me.map(x, y) = Integer.Parse(tokens(y))
Next y
Next x
End Using
End Sub
Bonus: Check for errors: what if the map doesn’t have the predefined width/height? Why hard-code this at all?
osRead.ReadLine() returns Nothing if the end of the input stream is reached. You call Peek to see if you're at the end of the input, but then, you proceed to read 12 lines without checking if you're at the end of the input in-between. If you have less than 12 more lines, you'll get the error you mentionned on temp.Split("-"), because temp will have a value of Nothing, so you can't call methods on it.
Also, just noticed something... your Map is 11x8, but you're reading 12 lines, and going through 9 values, you probably want to do:
For x As Integer = 0 To 10
or
For x As Integer = 1 To 11
Same thing for your other loop.
If temp is null (Nothing) in VB.Net, then you can't call methods on it.
Do a check for Nothing before attempting to do anything with the value.
So Just to be sure you have updated your code to look something like this:
If temp IsNot Nothing
'tempLine stores the split read line
Dim tempLine() As String
'splits readline into - ERROR Not set to an instance
tempLine = temp.Split("-")
'adds values to multidimensional array
For y As Integer = 0 To 8
Me.map(x, y) = tempLine(y)
Next y
End If
And you are STILL getting the null reference exeption?
also make sure that your StreamReader is properly initialized... if the initialization fails (perhaps because of a bad URI) then attempting to call osRead.peek() will throw the "object not set to an instance" error.
Related
I've ran into an issue with my code for the last week or so, and its been killing me trying to figure out what's wrong with it. I've extracted and isolated the issue from my main project, but the issue still isn't apparent.
Essentially, I have a function that usually does a lot of stuff, but in this example just changes 1 element in an array called FalseTable. Now, I have set this variable to be ByVal, meaning the original variable (ie: TrueTable) shouldn't change, however, it does! Here is the full code:
Dim TrueTable(7) As Char
Sub Main()
Dim FalseTable(7) As Char
For x = 0 To 7
TrueTable(x) = "T"
Next
For x = 0 To 7
FalseTable(x) = "F"
Next
Console.WriteLine("before")
For x = 0 To 7
Console.Write(TrueTable(x))
Next
Console.WriteLine()
Test(TrueTable)
Console.WriteLine("result")
For x = 0 To 7
Console.Write(TrueTable(x))
Next
Console.WriteLine()
Console.ReadLine()
End Sub
Function Test(ByVal FalseTable() As Char) As Char()
FalseTable(0) = "0"
Return FalseTable
End Function
Now, I used to think that it was the repetition of the name "FalseTable" in the function, however even if I change the function to:
Function Test(ByVal SomeTable() As Char) As Char()
SomeTable(0) = "0"
Return SomeTable
End Function
And not modify the rest, the issue still persists - for some reason, TrueTable is being updated when it shouldn't due to the ByVal status.
Any help with this would be greatly appreciated; it's probably something stupid that I've overlooked, but it's pulling my hair out!!
Many thanks,
Alfie :)
If you don't want to change the TrueTable, define another Array and copy TrueTable to it.
Here's the code you can refer to.
Dim TrueTable(7) As Char
Dim TrueTable2(7) As Char
Sub Main()
For x = 0 To 7
TrueTable(x) = "T"c
Next
Console.WriteLine("before")
For x = 0 To 7
Console.Write(TrueTable(x))
Next
Console.WriteLine()
TrueTable.CopyTo(TrueTable2, 0)
Test(TrueTable2)
Console.WriteLine("result")
For x = 0 To 7
Console.Write(TrueTable(x))
Next
Console.WriteLine()
Console.ReadLine()
End Sub
Result:
To understand why that happens just imagine this scenario.
You have a regular TextBox1 (this will be your TrueTable), now you want to pass the object TextBox1 to a function, something like this:
Function Test(ByVal TextBoxAnything as TextBox) As String
TextBoxAnything.Text = "0"
Return ""
End Function
Do you understand that you're passing thru TextBox1 and once inside the function Test the object TextBoxAnything is just TextBox1, anything you do to TextBoxAnything you're just doing it to TextBox1. TextBoxAnything doesn't exist, it just points to Textbox1. That's why the value of your TrueTable is also changed.
When this code is ran, Visual Studio gives the error:
InvalidOperationException was unhandled
List that this enumerator is bound to has been modified. An enumerator can only be used if the list does not change.
Dim counter As Integer
For Each x In lstWinners.Items
If x = lstWinners.SelectedItem Then
counter += 1
End If
Next
Here's a screenshot:
http://i.cubeupload.com/lIoWDg.png
EDIT:
This can be fixed by adding this line at the beginning:
Dim anything as string = lstWinners.Text
But why does this error happen, and why does this fix it?
When you are going over the list with for each it kind of "locks" the array. A good way around this is to just copy the array for the iteration.
Just use Array.Copy(source, target, target.Length) where your source would be lstWinners.Items and target is an array you declare. Then do your for each loop on the array. Something like:
Dim counter As Integer
Dim tempcopy(lstWinners.Items.Count) As String
Array.Copy(lstWinners.Items, tempcopy, tempcopy.Length)
For Each x In tempcopy
If x = lstWinners.SelectedItem Then
counter += 1
End If
Next
Perhaps it is seeing the = operator as the assignment operator instead.
Try this code instead:
Dim counter As Integer
For Each x In lstWinners.Items
If x Is lstWinners.SelectedItem Then
counter += 1
End If
Next
Is explicitly compares equality on objects, so it removes any potential ambiguity.
Assuming that your items are strings and you want to count the items with the same text then you could use
Dim counter As Integer
Dim x = lstWinners.SelectedItem.ToString()
counter = lstWinners.Items.Cast(Of String).Count(Function(z) z = x)
However, your code should not give that error unless there is something else that is running and modify your list (a separate thread?)
The title is to make this easy to find for others having this error. I'm new to Threading, so this is really giving me heck. I'm getting this runtime error that crashed Cassini. This is code that I'm maintaining originally developed as a website project in VS 2003 and converted to a VS 2008 website project.
Important Info:
The number of objects in the manualEvents array is 128 in this case.
products is an array of Strings
Need to support .NET 2.0
For Each product As String In products
If Not product.Trim().ToUpper().EndsWith("OBSOLETE") Then
calls += 1
End If
Next
Dim results(calls - 1) As DownloadResults
'Dim manualEvents(calls - 1) As Threading.ManualResetEvent '128 objects in this case.
Dim manualEvents(0) As Threading.ManualResetEvent
manualEvents(0) = New Threading.ManualResetEvent(False)
'NOTE: I don't think this will work because what is not seen here, is that
' this code is being used to populate and cache a long list of products,
' each with their own category, etc. Am I misunderstanding something?
'initialize results structures
'spawn background workers
calls = 0
For Each product As String In products
If Not product.Trim().ToUpper().EndsWith("OBSOLETE") Then
Dim result As New DownloadResults
'manualEvents(calls) = New Threading.ManualResetEvent(False)
'Moved above For Each after declaration of variable
result.params.product = product
result.params.category = docType
'result.ManualEvent = manualEvents(calls)
result.ManualEvent = manualEvents(0)
result.Context = Me._context
results(calls) = result
Threading.ThreadPool.QueueUserWorkItem(AddressOf ProcessSingleCategoryProduct, results(calls))
Threading.Interlocked.Increment(calls) 'Replaces below incrementation
'calls += 1
End If
Next
Threading.WaitHandle.WaitAll(manualEvents) 'CRASHES HERE
Thread Helper Function (for the sake of completion)
Public Shared Sub ProcessSingleCategoryProduct(ByVal state As Object)
Dim drs As DownloadResults = CType(state, DownloadResults)
Dim adc As New cADCWebService(drs.Context)
drs.docs = adc.DownloadADC(drs.params.category, drs.params.product)
drs.ManualEvent.Set()
End Sub
You don't need an array of 128 manual events to check for completion of all 128 threads.
Create only one manual reset event and a plain integer starting at 128. Decrement that integer using Interlocked.Decrement at the end of ProcessSingleCategoryProduct, and only signal the event when the count reaches zero:
if (Interlocked.Decrement(ByRef myCounter) = 0) myEvent.Set();
Then declare only one Threading.ManualResetEvent as opposed to an array of them, and you can call WaitOne instead of WaitAll on it, and you are done.
See also usr's comment for an easier alternative in case you have .NET 4.
I am having trouble turning a set of data from a .txt file into arrays, basically, what i have in the text file is:
Eddy vbtab 20
Andy vbtab 30
James vbtab 20
etc..
I want to set up the names as a Names array, and numbers as number array.
Now what I have done is
strFilename = "CustomerPrices.txt"
If File.Exists(strFilename) Then
Dim srReader As New StreamReader(strFilename)
intRecords = srReader.ReadLine()
intRows = intRecords
For i = 0 To intRows - 1
intLastBlank = strInput.IndexOf(vbTab)
strName(intPrices) = strInput.Substring(0, intLastBlank)
dblPrices(intPrices) = Double.Parse(strInput.Substring(intLastBlank + 1))
But when I debug I get a problem "Object Reference not set to an instance of an object"
Can anyone give me some advise?
Thanks
Separate arrays are probably a bad idea here. They group your data by fields, when it's almost always better to group your data by records. What you want instead is a single collection filled with classes of a particular type. Go for something like this:
Public Class CustomerPrice
Public Property Name As String
Public Property Price As Decimal
End Class
Public Function ReadCustomerPrices(ByVal fileName As String) As List(Of CustomerPrice)
Dim result As New List(Of CustomerPrice)()
Using srReader As New StreamReader(fileName)
Dim line As String
While (line = srReader.ReadLine()) <> Nothing
Dim data() As String = line.Split(vbTab)
result.Add(new CustomerPrice() From {Name = data(0), Price = Decimal.Parse(data(1))})
End While
End Using
Return result
End Function
Some other things worth noting in this code:
The Using block will guarantee the file is closed, even if an exception is thrown
It's almost never appropriate to check File.Exists(). It's wasteful code, because you still have to be able to handle the file io exceptions.
When working with money, you pretty much always want to use the Decimal type rather than Double
This code requires Visual Studio 2010 / .Net 4, and was typed directly into the reply window and so likely contains a bug, or even base syntax error.
Public Structure testStruct
Dim blah as integer
Dim foo as string
Dim bar as double
End Structure
'in another file ....
Public Function blahFooBar() as Boolean
Dim tStrList as List (Of testStruct) = new List (Of testStruct)
For i as integer = 0 To 10
tStrList.Add(new testStruct)
tStrList.Item(i).blah = 1
tStrList.Item(i).foo = "Why won't I work?"
tStrList.Item(i).bar = 100.100
'last 3 lines give me error below
Next
return True
End Function
The error I get is: Expression is a value and therefore cannot be the target of an assignment.
Why?
I second the opinion to use a class rather than a struct.
The reason you are having difficulty is that your struct is a value type. When you access the instance of the value type in the list, you get a copy of the value. You are then attempting to change the value of the copy, which results in the error. If you had used a class, then your code would have worked as written.
try the following in your For loop:
Dim tmp As New testStruct()
tmp.blah = 1
tmp.foo = "Why won't I work?"
tmp.bar = 100.100
tStrList.Add(tmp)
Looking into this I think it has something to do with the way .NET copies the struct when you access it via the List(of t).
More information is available here.
Try creating the object first as
Dim X = New testStruct
and setting the properties on THAT as in
testStruct.blah = "fiddlesticks"
BEFORE adding it to the list.