Issue with ByVal and Arrays in Functions (VB.NET) - vb.net

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.

Related

Visual Basic Validation

I'm new to Visual Basics, and I tried implementing input validation for the Mark input in my application where the input can't be empty or < 0 or > 100 or isnumeric(Mark) = False or else an error message would be displayed. However, I tried leaving the input as empty or entering alphabetical values but it returned me a run time error instead. Could anyone assist me in some explanations as well as how to overcome this? Apologies if this is a dumb question.
Note: The data type for the variable "Mark" is set as single. Thank you for your time!
Sub InputMark()
4: For I = 0 To 3
Console.WriteLine("Please enter test marks for test " & I + 1)
Mark(I) = Console.ReadLine
While Mark(I) = Nothing Or Mark(I) < 0 Or Mark(I) > 100 Or IsNumeric(Mark(I)) = False
Console.WriteLine("Please enter valid test marks.")
GoTo 4
End While
Next
End Sub
A couple of things:
Turn option strict on: https://github.com/dday9/vb.net-tutorial/blob/master/Section%201/Chapter%201.md
Using Single.TryParse on the result of ReadLine.
ABANDON GOTO'! In this case a simple Do loop will suffice.
Take a look at this example:
Option Strict On
Option Explicit On
Imports System
Public Module Module1
Public Sub Main()
' Declare an array of Single values
Dim marks(3) As Single
' Loop from 0 to n (in this case 3)
For counter As Integer = 0 To marks.Length - 1
' Declare a variable to check if the conversion was succesful
Dim success As Boolean = False
' Start a Do/Until loop
Do
' Propt the user to enter something
Console.WriteLine("Please enter test marks for test " & counter + 1)
' Check if the conversion from ReadLine to a Single is valid
success = Single.TryParse(Console.ReadLine(), marks(counter)) AndAlso marks(counter) >= 0 AndAlso marks(counter) <= 100
' If not then scold the user
If (Not success) Then
Console.WriteLine("Please enter valid test marks.")
End If
Loop Until success
Next
' Debug - just print out the values of marks
For Each mark As Single In marks
Console.WriteLine(mark)
Next
End Sub
End Module
Live Demo: https://dotnetfiddle.net/guoAzP
The data type for the variable "Mark" is set as single.
One of the nice things about strongly-typed platforms like .Net is most of your validation rules are already enforced. You will not be able to assign a non-number value to Mark, and as a value-type it can't even be null (VB.Net's Nothing will evaluate to 0 for a Single type).
So what we need to do instead is back up, and find where the data is received from the user, before it's assigned to the Mark variable. Somewhere you'll have code like Mark(I) = Console.Readline(), or similar. That's where your testing needs to be. Check the string value received from the user before assigning it to the Single value. And when you come to that point, the best way to accomplish this testing is with the Single.TryParse() function. Then, you can verify the parsed Single value is within the correct range.
Okay, with more complete code we can start to give you some real improvements.
One suggestion is to think in terms of returning a value. It's better design to let this function return the array, rather than communicating through a global variable. I mention that here because it will also mean changing how you call this function.
That out of the way:
Function InputMarks() As Single()
Dim result(3) As Single
For I As Integer = 0 To 3
Dim ErrorMsg As String = ""
Do
Console.Write($"{ErrorMsg}Please enter test marks for test {I+1}: ")
Dim input As String = Console.ReadLine()
ErrorMsg = $"{vbCrLf}Please enter valid test marks.{vbCrLf}{vbCrLf}"
While Not Single.TryParse(input, result(I)) OrElse result(I) < 0 OrElse result(I) > 100
Next
Return result
End Function
No GOTO required or wanted.
If you haven't seen them yet, the $"strings" are using string interpolation and the OrElse operator instead of Or is to get short-circuiting behavior. Modern VB.Net should use OrElse and AndAlso instead of Or and And pretty much all the time.
In this case, using OrElse means we're sure Single.TryParse() completed successfully and we will have a valid number before attempting to verify the range. If the parse operation failed, the range tests won't even be attempted.
I'm going to break this into smaller functions.
First, here is a function to process a string, which returns either a number if it satisfies your conditions, or Single.NaN.
Private Function validateMarkOrNaN(input As String) As Single
Return If(Single.TryParse(input, validateMarkOrNaN) AndAlso 0 <= validateMarkOrNaN AndAlso validateMarkOrNaN <= 100, validateMarkOrNaN, Single.NaN)
End Function
Here is a second function which calls the first when it needs to process the user input. This function handles the flow of a single input. It can be called any number of times, requiring that you pass in the index only so it can be typed in the console.
Private Function getMarkInput(index As Integer) As Single
Dim result As Single
Do
Console.WriteLine($"Please enter test marks for test {index + 1}")
result = validateMarkOrNaN(Console.ReadLine())
If Single.IsNaN(result) Then Console.WriteLine("Please enter valid test marks.")
Loop While Single.IsNaN(result)
Return result
End Function
Your main program dictates the overall flow. Here you can declare how many marks will be read, and loop over your array, getting each input into each element.
Sub Main()
Dim numberOfMarks = 4
Dim Mark(numberOfMarks - 1) As Single
For i = 0 To numberOfMarks - 1
Mark(i) = getMarkInput(i)
Next
End Sub
Similar to your code, this will loop indefinitely as long as the user doesn't enter a valid float.

VBA ByRef Argument Type Mismatch When Using Function In If Statement

I am having a weird issue when I'm calling a function from another function inside an if statement. I defined a Sub, which I am using to test this function, and it calls on a function which relies on another function I use to compare values. The code is below, which should make things clear. Essentially, I don't understand how it's possible for the code to work fine in the print statement, and then throw an error in the GetMatch function. I appreciate any help.
Edit: All of a sudden everything works. Do debugging breakpoints affect the program? I haven't changed anything, but CStr() is no longer required when calling GetMatch. I haven't touched any of the subs or functions, but I did clear some breakpoints. If I find what caused it, I'll post a solution. Thanks for the help everyone.
Edit2: Maybe this is a bug with VBA? If I add the CStr() option to the indexOrder(...) calls, things work. Before, without the CStr() options, things did not work. Now, strangely enough, after using the CStr(), I am able to remove the CStr()'s entirely from the program, and things work again. It breaks if I undo to the point where they weren't there originally though. I don't know what this could be, but if anyone has an explanation, I'm very interested. Thanks
Sub testFind()
Dim SortOrder() As Variant
Dim indexOrder() As Variant
SortOrder = Array("Contact Email", "Last Name", "First Name", "Attempt #", "Customization", "Template #")
indexOrder = Array("First Name", "First Name", "Template #", "Customization")
findAndReplace(indexOrder, SortOrder)
End Sub
Function findAndReplace(indexOrder As Variant, list As Variant) As Variant
Dim indexLength As Integer
Dim listLength As Integer
Debug.Print TypeName(indexOrder(0)) ' Identifies as String
indexLength = getVariantLength(indexOrder)
listLength = getVariantLength(list)
Debug.Print GetMatch(CStr(indexOrder(1)), CStr(indexOrder(1))) ' This works fine. Returns 0 as it should
If GetMatch(indexOrder(1), indexOrder(1)) = 0 Then ' Fails with ByRef error
Debug.Print ("Why don't I work?")
End If
End Function
Function GetMatch(A As String, B As String) As Integer
A = Trim(A)
B = Trim(B)
If (IsEmpty(A) Or Trim(A) = "") Then
GetMatch = 1
Exit Function
ElseIf (IsEmpty(B) Or Trim(B) = "") Then
GetMatch = -1
Exit Function
End If
GetMatch = StrComp(A, B, vbTextCompare)
End Function
Function getVariantLength(vari As Variant) As Integer
If IsNull(index) Then
getVariantLength = 0
Else
getVariantLength = UBound(vari) - LBound(vari) + 1
End If
End Function
You don't have a Sub vartest() or Function vartest(), it's trying to call a sub/function that doesn't exist, or at least it isn't included.
Edit: You aren't doing anything with the function. A function will return a value, a sub will 'do' something. You need to assign a variable to whatever it returns, or do a MessageBox or some other way of returning the value.
The next issue is it's trying to call a function getVariantLength() that isn't defined or listed.

Storing input into Arrays in Visual Basic

I'm just got into Visual Basic and I'm trying to "recode" my programs from java into VB. But my main problem is how to do that, i don't the syntax too much. I have read some but I find it hard(I'm a slow learner :P).
Edit:
Here is the code I am trying:
Module Module1
Dim arrays(5) As String
Sub Main()
Console.WriteLine("Enter your Names:")
For i As Integer = 0 To arrays.Length
arrays(i) = Console.ReadLine
Next i
For Each arr As String In arrays
Console.WriteLine(arr)
Next
Console.ReadLine()
End Sub
End Module
At some point, whenever I run it and try to input, it goes beyond the number of index. And doesn't write the inputs :P
Since it is a zero based array, you need to get the length - 1. Your array is set to 5, so it has 6 elements and arrays. Length = 6 where your loop needs to be 0 to 5.
Module Module1
Dim arrays(5) As String
Sub Main()
Console.WriteLine("Enter your Names:")
For i As Integer = 0 To arrays.Length - 1
arrays(i) = Console.ReadLine
Next i
For Each arr As String In arrays
Console.WriteLine(arr)
Next
Console.ReadLine()
End Sub
End Module

VB.net Using a string to change something.Text

I need to work in VB.net for school and have encountered a problem.
The program works like this:
I've got a store page with buttons to buy them, they each have a name with the item and "_buy" after it. So if I wanted to buy a laptop, I'd press a button named laptop_buy.
What I want then is to make the event call the fuction Buy(laptop), so I can use the laptop ID later on.
I also have things named like laptop_level and laptop_price. I want to change them when I click the button. So I created this function:
Private Sub laptop_buy_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles laptop_buy.Click
Buy("laptop")
End Sub
And
Function Buy(ByVal item)
Dim itemlevel As String = item + "_level.Text"
itemlevel += 1
End Function
So this should change the laptop_level.Text when I click laptop_buy.
But it isn't working.
Help is greatly appriciated!
You can use ControlCollenction.Find method to get your label.
Then you need to cast it's text value to Integer (I recommend using Int32.TryParse method for that) and then add 1 and return the result to the label text. Something like this should do the trick.
Sub Buy(ByVal item As String)
Dim level As Inetger
Dim ItemLabel as Label;
Dim ChildControls As Control() = Me.Controls.Find(item & "_level", True)
If ChildControls.Length > 0 Then
ItemLabel = ChildControls(0)
End If
If not isNothing(ItemLabel) AndAlso Int32.TryParse(ItemLabel.Text, level) Then
ItemLabel.Text = (Level + 1).ToString
End If
End Sub
Note: Code was written directly here, and it's been a long time since my vb.net days, so there might be some mistakes in the code.
Your trying to add 1 to a string.
A 'String' is a series of text characters, so the '1' in your text box is the character '1' not the number. You need to first conver the text into an 'integer'
Try:
dim itemlevel as integer = TryCast(item, integer)
If IsNothing(itemlevel) = false then
itemlevel = itemlevel + 1
Return itemlevel
end if
MSDN TryCast

Error Reading string into array

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.