I want to check if a property from a class is an array (only concerned about numerical arrays here, NOT character arrays [i.e. strings]). I then want to iterate through the array (i.e. 'do something' with each element). See my attempt below. Thanks!!
edit:
So, a little more info... neither IsArray nor my method shown has worked thus far to check for an array. MSDN suggestions "typeof(Array).IsAssignableFrom(type)", but I wasn't sure how to make that work with the property info here. But maybe someone else knows how to use them and I just didn't use properly.
Within the "Class3" I define an array but to not dimension it. I use "redim" when I instantiate it in another thread and load it up prior to passing it to this function. When I insert a breakpoint in the code here, I can look at "myobject" and see the array elements and values, but really I'm looking to cleanly use the propertyinfo type to generalize this method. I also need to be able to index into the array once I've determined that it is an array...again using propertyinfo, not "myobject" directly.
Public Class Class2
Private Shared filelock As New Object
Public Shared Sub write2file(ByVal myobject As Class3)
SyncLock filelock
Dim sb As New StringBuilder
Using sw As StreamWriter = New StreamWriter(File.Open(newfilename, FileMode.Append, FileAccess.Write, FileShare.None))
'Dim pinfo() As PropertyInfo = GetType(Class3).GetProperties
Dim pinfo() As PropertyInfo = CType(myobject.GetType.GetRuntimeProperties, PropertyInfo())
sb.Clear()
For Each p As PropertyInfo In pinfo
If Not p.GetIndexParameters.Length > 0 Then 'if property is not an array
sb.Append(p.GetValue(myobject)).Append(",")
Else ' if property is an array
For x As Integer = 0 To p.GetIndexParameters.Length - 1
sb.Append(p.GetValue(myobject, New Object() {x})).Append(",") 'append each value from array to the stringbuilder in .CSV format
Next
End If
Next
sw.WriteLine(sb) 'write string to file
End Using
End SyncLock
End Sub
End Class
Well, this is not the prettiest thing to do (I somehow don't feel comfortable when comparing the type with a string... if anyone knows better please let me know), but I have tested it and it works:
For Each p As PropertyInfo In pinfo
Dim typeString As String = p.PropertyType.Name.ToString
If typeString = "Int32[]" Then 'if property is not an array
sb.Append(p.GetValue(myobject)).Append(",")
Else ' if property is an array
For x As Integer = 0 To p.GetIndexParameters.Length - 1
sb.Append(p.GetValue(myobject, New Object() {x})).Append(",") 'append each value from array to the stringbuilder in .CSV format
Next
End If
Next
Is it what you were looking for?
only concerned about numerical arrays here, NOT character arrays [i.e. strings]
If by "numerical" you mean that it could be any type (not just Integer), and you are 100% sure that the array it is either numerical or string (I mean, no boolean arrays for instance), then you can modify it to this:
If typeString.EndsWith("[]") And typeString <> "String[]" Then
I hope it helps...
Replace the If block with something like this and it seems to work with a trivial Class3 implementation with an Int32() array property:
If p.PropertyType.IsArray Then
' Untested loop code for array case
Else
' Untested code for scalar (non-array) case
End If
No one seems to have recommended the PropertyType property yet, but that's pretty crucial.
Related
Dim a As String()
Dim b() As String
One says that a is a type of string array
Another isn't very clear. Are they completely equivalent?
Why the 2 ways?
I supposed the first way is saying a is a String array instead of just string.
I am not so sure about the reasoning of Dim b() As String
I give it a try. The result is that b is a String() that points to nothing.
a is an integer whose value is -1
I am confused.
Why?
In functions we declare that a function accepts an array with something like this
Protected Overrides Async Function createNormalLimitOrderMultiple(orderList As BasicSimpleOrder()) As Task
Here, BasicSimpleOrder() simply tell that orderList is of type BasicSimpleOrder() and not BasicSimpleOrder.
So why doesn't Dim a As String() work
In most situations you could treat them the same, however there are some differences.
Array size: You cannot specify the upper-bounds of an array if you put the parenthesis after the type, otherwise you will receive an error.
Dim foo As String(1) ' Compilation Error: Array bounds cannot appear in type specifiers.
Property Definitions: You cannot define an array if you put the parenthesis after the name of a property. The reason for this is because you can optionally include parenthesis after a property name definition and then optionally include a parameter list inside the parenthesis.
Public Class MyClass
Public Property Property1() As String ' not an array
Public Property Property2 As String() ' this is an array
End Class
Here is the documentation on property statements in Visual Basic .NET: https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/property-statement
I am having trouble with this pesky error message (see title)
I have seen it on a lot of posts on this site and others and it is usually some dumb mistake and try as I might I cannot see what dumb thing I am doing.
Public Property Get Contents() As Variant
Contents() = pContents()
End Property
Public Property Let Contents(Values() As Variant)
pContents = Values()
End Property
Public Property Get Content(Index As Integer) As String
Content = pContents(Index)
End Property
Public Property Let Content(Index As Integer, Value As String)
...
The aim being that the pair of get, let two allow the entire array to be read/written and the second allows read/writing to individual elements of the array by index.
According to this: https://msdn.microsoft.com/en-us/library/office/gg251357.aspx
Let and Get property statement thingies (Yes, I am fairly new to this) for a class in a class module have to meet certain requirements (which I am curious as to why exactly?). As far as I can tell, mine meet those requirements:
My Let has one more parameter (2) than my Get (1) - I'm assuming here that the "as string" outside of the brackets doesn't count as an argument.
Also I use the same types (integer and string) in both the Let and Get, I have no need for a Set so that shouldn't be a problem. I have also tried changing the names of the parameter variables to no avail.
So please help me, what is wrong and how can I avoid this mistake in the future?
Well I seem to have resolved the issue myself, posting the answer here in case it helps others! :)
Precautions to make when setting class properties to arrays (and reading them)
1) be careful with brackets(): don't use them in the Let statement
Public Property Let ArrayProperty (newArray as variant)
as opposed to the tempting
Public Property Let ArrayProperty (newArray() as variant)
Similarly, don't use them when using this property e.g:
Class1.ArrayProperty = myArray
no brackets after myArray
2) Check that your arrays are not empty in property (myArray) Let statements
Link here VBA: Don't go into loop when array is empty to the answer that helped me accomplish this, it seems you have to build your own function or use error handling to do it,
IsEmpty(myArray)
won't work.
and the code snippet adapted for my purposes (credit to original snippet goes to CreamyEgg):
Public Function IsEmptyArray(TestArray) As Boolean
Dim lngUboundTest As Long
lngUboundTest = -1
On Error Resume Next
lngUboundTest = UBound(TestArray)
On Error GoTo 0
If lngUboundTest >= 0 Then
IsEmptyArray = False
Else
IsEmptyArray = True
End If
End Function
3) remember that you might need to redim your property array to the Ubound(newArray), e.g.
Public Property Let Contents (newArray as variant)
'redim pArray to size of proposed array
ReDim pArray(1 To UBound(newArray))
'above line will throw an exception if newArray is empty
pArray = newArray
end Property
4) to deliberately make a class property an empty array, I used a temporary array variable, publicly declared outside of a sub at the start of a standard module
Public TempArray() As Variant
'outside of Sub ^^
Sub SetClass1ArrayToEmpty
Erase TempArray
class1.ArrayProperty = TempArray
End Sub
the erase method will make an array empty, i used it since I occasionally used TempArray to make arrays of size 1 and wanted to make sure it was empty
5) good news, setting a range to a class property seems to work the same way as setting a range to an array, I used application.transpose(myRange) to avoid problems with one column becoming a 2 dimensional array
class1.ArrayProperty = Application.Transpose(Range(myRange))
and there you have it, here is the class that works (no compile error)
Public Property Get Contents() As Variant
Contents() = pContents()
End Property
Public Property Let Contents(Values As Variant)
'checks for an empty array being passed to it first
If IsEmptyArray(Values) Then
Erase pContents
Else
'redim pContents to size of proposed array
ReDim pContents(1 To UBound(Values))
pContents = Values
End If
End Property
Public Property Get Content(Index As Integer) As String
Content = pContents(Index)
End Property
Public Property Let Content(Index As Integer, Value As String)
Select Case Index
Case Is < 0
'Error Handling
Case Is > UBound(pContents) + 1
'Error Handling
Case Is = UBound(pContents) + 1 'append to end of array
ReDim Preserve pContents(UBound(pContents) + 1)
pContents(Index) = Value
Case Else 'replace some middle part of array
pContents(Index) = Value
End Select
End Property
Hope this helps some peeps!
I want to create a function that takes an argument of a reference to an array of at least a certain length, so that the function will know that there is enough space for it to write all the data it needs to write to the array.
Is this possible in VB.NET?
At the moment I am ReDim'ing the referenced array but I'm not sure if this is really working or not. (I guess I can test this method and break it by passing an array to small and see, will try that momentarily)
Public Function Foo(ByRef data() As Byte) As Boolean
If Data.Length < 4 Then
ReDim Preserve ProductId(4)
End If
' Other operations that put 4 bytes on the array...
Return True
End Function
Even if that method works, I'm not convinced that re-sizing the users array is really that great of an idea in comparison to just informing them that the parameter specifies a length of 4 somehow... Is there a better way of managing this?
Your function should take in a stream instead.
Public Function Foo(ByVal stream As Stream) As Boolean
'Write bytes to stream
End Function
For eaxmple you can call your method using a MemoryStream
Dim stream = new MemoryStream()
Foo(stream)
Dim array = stream.ToArray() 'Call ToArray to get an array from the stream.
No as far as I know you can't specify the size of the array in the parameters list.
You can however, check the size of the array like you are currently doing and then throw an ArgumentException. This seems to be one of the most common ways to validate data at the start of a method.
I'd actually try something similar to what you're doing here, but like this:
Public Function Foo(ByRef data() As Byte) As Boolean
If Data.Length < 4 Then
Return False
End If
'Other operations...
Return True
End Function
Or maybe this:
Public Function Foo(ByRef data() As Byte) As String
If Data.Length < 4 Then
Return "This function requires an array size of four"
End If
'Other operations...
Return "True"
End Function
Then, in your calling function, this:
Dim d As Boolean = Convert.ToBoolean(Foo(YourArray))
Then you can bubble up the error to the user, which is what you were looking to do, right?
I have one 2D array that I'm wanting to populate into another. I got a bit mixed up between lists, dictionaries and simple arrays, so I'm thinking I have two different types or array. The edited code with some attempts and resulting errors:
Dim _inifile As String = "C:\Users\Steve\Scripts\Projects\IniRecEdit\Tests\insrow.ini"
Public IniLines() As String = File.ReadAllLines(_inifile)
Public _ini(IniLines.Length - 1)() As String
For I As Integer = 0 To IniLines.Length - 1
_ini(I) = IniLines(I).Split("="c)
Next
'.....code
Dim _tempini(Lines.Length - 1, SQSIZE - 1) As String
Dim tagrow As Integer
Dim tagcol As Integer
Dim taglist() As String
Dim RowSel As Integer = 1
Dim cControl As Control
For Each cControl In Me.Panel1.Controls
If cControl.Tag <> Nothing Then
taglist = Split(cControl.Tag, ","c)
If taglist(0) = "Cell" Then
tagcol = Val(taglist(1))
tagrow = Val(taglist(2))
If tagrow <= RowSel Then
If tagcol = 0 Then
_tempini(tagrow, tagcol) = _ini(tagrow)(tagcol)
Debug.WriteLine("Lower or equal then Selected row. 1st Column. From ini to row:" & tagrow)
' EDIT etc etc... more code here
Next cControl
' DIFFERENT CODE TRIED AT THIS STAGE to transfer from one array to the other:
ReDim _ini(Lines.Length - 1)
For countrow As Integer = 0 To _tempini.GetLength(0) - 1
For countcol As Integer = 0 To _tempini.GetLength(1) - 1
_ini(countrow) = _tempini(countrow)._tempini(countcol)
Next
Next
' Produces: Error 2 Number of indices is less than the number of dimensions of the indexed array.
ReDim _ini(Lines.Length - 1)
For countrow As Integer = 0 To _tempini.GetLength(0) - 1
For countcol As Integer = 0 To _tempini.GetLength(1) - 1
_ini(countrow)(countcol) = _tempini(countrow, countcol)
Next
Next
'Produces: Additional information: Object reference not set to an instance of an object.
As I say, I'm not even sure whether I'm using lists of not for "_ini" The Locals Window on Visual Studio shows the vars as:
_ini is "String()()"
_tempini is "String(,)"
I'm increasingly aware I need to go back to basics with vb and learn all of the concepts involved. However, a quick assist would let me try and complete this thing I'm knocking together with blu-tack and string :)
You are actually using arrays. The declaration of a list would look like this:
Dim myList As New List(Of String)()
A list has always one dimension and the initial size of a list is 0 (unless you pass an enumeration to the constructor). You have to add items one by one using the Add method. This makes the list grow dynamically.
I assume that your ini-file looks like this
key1 = value1
key2 = value2
key3 = value3
Also I assume that the value does not contain an equal sign, otherwise I would look for the first instance of an equal sign an split the string there "manually".
Using a two dimensional array seems not appropriate in order to store key-value-pairs. There is a handy struct KeyValuePair(Of TKey, TValue) you could use if you need a string or a list. Using an array of KeyValuePairs has the advantage that you will not have to deal with two-dimensional arrays.
However, if you want to look up values for given keys, the Dictionary(Of TKey, TValue) is more appropriate, as it is optimized for a very fast lookup.
Dim dict As New Dictionary(Of String, String)
For i As Integer = 0 To IniLines.Length - 1
Dim parts = IniLines(i).Split("="c)
dict.Add(parts(0).Trim(), parts(1).Trim())
Next
Then you can retrive a value like this
Dim value As String
If dict.TryGetValue("some key", value) Then
' Use the value
Else
' Sorry, key does not exist
End If
Or if you are sure that the key you are looking for really exists, you can simply write
value = dict("some key")
TLDR
(1) Working with Arrays...
(2) Why are you getting "Object reference
not set to an instance of an object."
(3) Multidimensional Arrays
and Arrays of Arrays
(4) Why are you getting "Number of indices is
less than the number of dimens..."
(5) What I would do if I were
you...
Working with Arrays : Declaration(1) and Instanciation(2)
(1) declaration is when you write the name of a variable and its type. You can then access the object reffered by this variable in your code.
(2) we can talk about (variable/object) instanciation when an actual object has been created and registered somewhere in the memory. You can then manipulate that object via something that points to that object : the variable.
A declaration doesn't always mean the actual object is created. (I won't dig in ValueType and ReferenceType, sorry guys, I'm just cutting corners while explaining things without the complex verbose of strict dotNet)
Arrays declaration is different from instanciation :
Dim myArray As String()
This is just a declaration. Your Array is Nothing at this time and therefore contains nothing. Calling something like myStringVariable = myArray(0) will throw an Exception (Object reference not set to an instance of an object) meaning your array declaration points to nowhere because you haven't created an object of type String() and made your Array declaration points to that object.
Dim myArray() As String
This is the same declaration as above. Which one is correct ? I don't know and it's a matter of taste. VB is known for letting you do things the way you want. If you're good, you'll be able to use the one or the other like breathing. In fact, I really don't care.
Dim myArray As String() = new String() {}
This is just an instanciation of your array. Your array is registered in memory, but is of length 0 (zero) and contains no single item (Length = 0).
Remeber the declaration above ? (myArray() As String or myArray As String()) In fact, there is a difference in the two syntax when you talk about instanciation :
you can write : Dim myArray(3) As String
but you can't write : Dim myArray As String(3)
The later throws an exception. The variable type (String) cannot have indexes delimiters.
Dim myArray(3) As String '...
... also declares an Array and instanciate it with 4 predefined items like below.
Dim myArray As String() ' this is a declaration.
Redim myArray(3) ' this is an instanciation with a defined size.
This is one another way to define the number of items in your array using the Redim keyword. You have 4 "slots" in that Array from index [0] to index [3]. Your array looks like this :
[0][""]
[1][""]
[2][""]
[3][""]
.
Dim myArray As String = New String(3) {}
This is the same as above, but in one line : you're declaring and instanciating an Array of String that contains 4 items of type String which are all empty string (they are not Nothing)
Dim myArray() As String = { "One", "Two", "Three", "Four" }
This is a one line declaration, instanciation and items-setting :
[0]["One"]
[1]["Two"]
[2]["Three"]
[3]["Four"]
Redim
Redim is used to redifine the number of items in an Array. One dimensional arrays just behaves the way they are supposed to.
Suppose you have defined your array as :
Dim myArray() As String = { "One", "Two", "Three", "Four" }
Obviously, you have four String. Then use Redim :
Redim myArray(6) ' to get 7 items ..!
This is what you get :
[0][""]
[1][""]
[2][""]
[3][""]
[4][""]
[5][""]
[6][""]
You can resize an Array, but to keep the items inside, you'll have to use a keyword : Preserve
Redim Preserve myArray(6)
myArray(4) = "Five"
'myArray(5) = "Six"
myArray(6) = "Seven"
Because I've commented out the setting of the sixth item, the array will look like this :
[0]["One"]
[1]["Two"]
[2]["Three"]
[3]["Four"]
[4]["Five"]
[5][""] ' Empty String.
[6]["Seven"]
What happens if you Redim Preserve the Array above twice while killing some items ?
Redim Preserve myArray(2)
Redim Preserve myArray(6)
' You get :
[0]["One"]
[1]["Two"]
[2]["Three"]
[3][""]
[4][""]
[5][""]
[6][""]
You've lost items 3..6 ! Don't expect to get them back.
Public _ini(IniLines.Length - 1)() As String
What this code does ? Writing Dim myArray(3)() As String ("3" could be any positive or null integer) results in the following Array structure :
[0][Nothing]
[1][Nothing]
[2][Nothing]
[3][Nothing]
Why are you getting Nothing but not a bidimensional array ? Not even empty strings ?
It's because the declaration above creates a ONE dimensional Array of Array (of the type defined, String here)
VB.net allows you to declare an Array of Array this way (I don't know yet if C# can - but I really don't care : I never, and will never use this type of syntax) This is not a bidimensional Array. To define the values, you'll have to create each instance of the contained Array per item (or line) and assign it to your Array.
In the declaration above, while you have explicitely initialized your Array, its contents [0..3] are still simple declarations that points to no instance of anything, thus, explaining the "Nothing" thing.
Then upon getting datas from your file, your _ini variable could look like this, depending on the content of your file (that's why I asked you to provide a sample of the content of your file) :
[0][ ["Key0" | "Value0"] ]
[1][ ["Key1" | ""] ]
[2][ [""] ]
[3][ ["Key3" | "Value3" | "OtherData" | "MoreData" | "EvenMoreData"] ]
...
Reading a (String) value can be done like follows :
Dim myStringVariable As String = _ini(0)(1) ' Gets "Value0"
=> Be carefull when you use Array or Arrays along with
multidimentional arrays. They are not the same !
Let's pick the code below :
ReDim _ini(Lines.Length - 1)
For countrow As Integer = 0 To _tempini.GetLength(0) - 1
For countcol As Integer = 0 To _tempini.GetLength(1) - 1
_ini(countrow)(countcol) = _tempini(countrow, countcol)
Next
Next
Your _tempini is a bidimentional array of String.
Your _ini is an one dimensions array of array of String.
Scan this line :
_ini(countrow)(countcol) = _tempini(countrow, countcol)
1) _ini(countrow) contains Nothing (whatever the value of countrow) = contains NO Array (of String) because you've just called ReDim _ini(Lines.Length - 1) right before the For loop.
2) Therefore your assignation _ini(countrow)(countcol) = ... is the same as running the following at runtime :
Nothing(countcol) = _tempini(countrow, countcol)
Nothing is not an Array, so the (countcol) indexing has no meaning : you're indexing an item value to an object expected to be an Array that doesn't even exist.
That's why you get the Exception Object reference not set to an instance of an object.
Multidimensional Arrays / Redim / Preserve
The declaration and instanciation above still apply !
Public myBiDiArray(,) As String ' Declaration ONLY : bi dimensional array
Public myBiDiArray(2,1) As String ' Declaration AND Instanciation
Public myBiDiArray(,) As String = New String(2, 1) {} ' Dec. + Inst.
Public myBiDiArray(,) As String = _
{ {"One", "Un"}, {"Two", "Deux"}, {"Three", "Trois"} }
' ^^ Declaration + Instanciation + Item Setter
Public myBiDiArray(,) As String = _
New String(,) { {"One", "Un"}, {"Two", "Deux"}, {"Three", "Trois"} }
' ^^ Declaration + Instanciation + Item Setter
Let's have a Bidimensional Array of String with rows and columns...
Dim myArray(2, 1) As String ' 3 lines, two columns
This array contains the following items :
Line 0 = [""][""]
Line 1 = [""][""]
Line 2 = [""][""]
Now you want to resize the Array and use the Redim (only) Keyword...
Redim myArray(5, 3) ' ...
...which would set the lines to 6 and columns to 4 :
' Array Size = 6, 4
Line 0 = [""][""][""][""]
Line 1 = [""][""][""][""]
Line 2 = [""][""][""][""]
Line 3 = [""][""][""][""]
Line 4 = [""][""][""][""]
Line 5 = [""][""][""][""]
That's great !
Now let's set a value in Row(0) and Column(0)
myArray(0, 0) = "Cell,0,0"
' You get :
Line 0 = ["Cell,0,0"][""][""][""]
Line 1 = [""] [""][""][""]
Line 2 = [""] [""][""][""]
Line 3 = [""] [""][""][""]
Line 4 = [""] [""][""][""]
Line 5 = [""] [""][""][""]
But what happens with the Preserve Keyword ?
Redim Preserve myArray(3, 2) ' 4 lines and 3 columns
You get an exception : 'ReDim' can only change the rightmost dimension.
But you must know that this Exception is not handled by the debugger unless you explicitly write a Try/Catch routine that encapsulates the Redim Preserve Code. Otherwise, the application just exit the method/function containing that piece of code without any warning and your Array remains untouched !
To redim a multidimensional array (not only the last dimension) while preserving its content, you must create a new array of the desired size and copy the content of the former one to this new array, then make your former array point to that new one...
_ini(countrow) = _tempini(countrow)._tempini(countcol)
I'm not aware of such syntax in VB.Net. Perhaps you got this from another language or a Reference that adds extensions to Arrays (like System.Linq) which extends (?) the members of an Array to itself for some purpose.., but I'm not aware of that.
It looks to me like a simple syntax error. That's why you get the Exception Number of indices is less than the number of dimensions of the indexed array.
To get the (string) value of a bidimensional array, just write :
myStringValue = _tempini(countrow, countcol)
The error warning is not pointing to the systax error though : the debugger stopped at the closing backet and discarded the remaining piece of text :
_ini(countrow) = _tempini(countrow ... ' <- compiler expects a comma here.
Writing this will produce the following error :
myStringValue = _tempini(countrow, countcol)._tempini(countcol)
Compiler Error : 'tempini' is not a member of 'String'.
Again, don't mess with bidimentional arrays and arrays of arrays : Writing...
_ini(countrow) = _tempini(countrow, countcol) ' ...
... has no meaning ! Even if the syntax looks correct, the above code actually tries to assign a String value to an Array(Of String) variable, which will throw an InvalidCastException.
I'll be honest : Arrays are a great bit of fun with fixed/predefined size/dimensions. From the moment I'm working with unknown number of elements/items at runtime, I leave Arrays behind.
What I would do ?
Oh! just the same Olivier Jacot-Descombes advised you to do :) No surprise, a Dictionary is very handy in your situation.
I would also enable those two :
Option Strict On
Option Explicit On
and disable this one : Option Infer Off.
Another PERSONAL advice is to avoid using the Tag property of a control. This tag is of type Object.
some Casting should be done to get a cleaner code.
Controls are usual components you'll use anywhere. One day, I've imagined I have tied an object of type 'String' to a specific control while it actually contained a Date or even Nothing. Why ? Because I confounded two different projects and lost track of the whole thing.
pass your code to a colleague or a friend, and be sure he/she'll ask you to document it about what you've done with the Tag Property... that simply means no one want to discover the obscure underlying logic of a code involving Objects. People like to deal with strongly typed variables, easy to debug and fast performing at runtime.
Also avoid the use of Val(). Val() is very handy because it can almost convert anything. However, because of this nice feature, it adds a workload on the CPU. Again, if you know you're dealing with Int32 or Double, then use Int32.Parse() or Double.Parse() but not Val(). Most misuse of Val() on the web are related to type inference and implicit casting. Debugging such kind of glitches are a real pain. Use Val only when you have no alternative.
I think you now have almost everything you need to get your code working with little changes. I have not provided the code for the changes though :
I don't understand where your samples of code lands in your application logic
I don't know what kind of datas you're manipulating
I don't even know what's the purpose of the code.
^^ So I just addressed two things :
why you're getting errors...
since you're new to VB, I just provided a custom explanation of Arrays.
Was that the (not so) quick assist you were looking for ? I don't know. That's the best I could give right now.
OK. People have been really helpful, but not really up to totally recoding at this stage. I did find a work around, however. It aint pretty and probably is really bad practice! However, got it up and running with the code I've got.
Essentially, I just did the process in reverse, constructed a line from the components in _tempini, delimited by a "=" and then just re-ran the original procedure for populating the _ini array in the first place. A total work-around, but hope will help any other amateurs like me getting stuck in these early stages.
However, I would say follow these fellas advice! Look into list, dictionaries etc before starting your code.
The fix:
ReDim _ini(((_tempini.Length) / 2) - 1)
Dim _newiniline As String
_newiniline = ""
For countrow As Integer = 0 To (((_tempini.Length) / 2) - 1)
For countcol As Integer = 0 To 1
_newiniline = _newiniline & _tempini(countrow, countcol)
If countcol = 0 Then
_newiniline = _newiniline & "="
End If
_ini(countrow) = _newiniline.Split("="c)
Next
_newiniline = Nothing
Next
I've searched for an answer, but struggling to adapt my findings into my code/text file.
I have this text file;
20,Ben
10,Dave
7,Bob
Scores and names.
I want pull the data from the text file into a 2D array, for example:
array(0, 0)
array(1, 0)
array(0, 1)
array(1, 1)
array(0, 2)
array(1, 2)
which would translate to;
array(20)
array(Ben)
array(10)
array(Dave)
array(7)
array(Bob)
Thanks in advance
As mentioned in my comment,i would use a class for this.
For example Player with two properties: Name As String and Score As Int32. Then you can create a List(Of Player) instead. That's much more readable, reusable and maintainable and also less error-prone than fiddling around with indices.
Public Class Player
Public Property Name As String
Public Property Score As Int32
End Class
Here is a readable LINQ query that initializes a List(Of Player) from lines in a text-file:
Dim allPlayers = From line In System.IO.File.ReadLines("path")
Let Columns = line.Split(","c)
Where Columns.Length = 2
Let Score = Int32.Parse(Columns(0).Trim())
Let Name = Columns(1).Trim()
Select New Player With {.Score = Score, .Name = Name}
Dim playerList As List(Of Player) = allPlayers.ToList()
If you need an array use ToArray instead of ToList.
You can access alist like an array via indexer(list(0)) or via LINQ methods:
Dim firstPlayer As Player = playerList.FirstOrDefault() ' is Nothing if there are no players '
Console.WriteLine("Name of the player: " & firstPlayer.Name)
Console.WriteLine("His score is: " & firstPlayer.Score)
or in a loop:
For Each player In playerList
Console.WriteLine("Player {0} has scored {} points.", player.Name, player.Score)
Next
By the way, LINQ is useful to keep your code readable, under the hood it also uses loops. So you could simply use OrderByDescendending to order your players by score and output the top 3 with the highest score:
Dim best3Players = From player In playerList
Order By Player.Score Descending
Take 3
Select String.Format("{0}({1})", player.Name, player.Score)
Dim output = String.Join(", ", best3Players)
Windows.Forms.MessageBox.Show(output) ' since you have mentioned messagebox '
If you know the file is not going to be terribly big, the simplest approach would be to simply use the File.ReadAllLines method, which returns a single-dimension array of strings containing one item per line in the file. You could then loop through each line and use the String.Split method to split apart the two values from the line, for instance:
Dim lines() As String = File.ReadAllLines("leaderboard.csv")
Dim values(lines.Length - 1, 1) As String
For i As Integer = 0 to lines.Length - 1
Dim parts() As String = lines(i).Split(","c)
values(i, 0) = parts(0)
values(i, 1) = parts(1)
Next
However, it would be better if you created a class to store all of the data from each row, like this:
Public Class LeaderboardEntry
Public Property PlayerName As String
Public Property Score As Integer
End Class
Then, you could load the values into a list of those objects, like this:
Dim entries As New List(Of LeaderboardEntries)()
For Each i As String In File.ReadAllLines("leaderboard.csv")
Dim parts() As String = lines(i).Split(","c)
Dim entry As New LeaderboardEntry()
entry.Score = parts(0)
entry.PlayerName = parts(1)
entries.Add(entry)
Next
Then, instead of saying values(0, 0) to get the score of the first entry, you would say entries(0).Score, which is much more readable, especially if you add more fields to each line in the file. For more information on the value of using a class for something like this, check out my answer to this question.
Bear in mind however, that the above code assumes that each line is properly formatted. If the file contained any lines that did not include a comma, it would fail. If you need to handle that situation, you'd need to add some extra checking. Also, in the above scenario, the player name cannot contain any commas. The best way to handle that would be to use proper CSV formatting, like this:
30,"Doe, John"
And then your quotes would need to be escaped, etc., in which case, it would be worth using the TextFieldParser class to read the CSV file rather than reinventing the wheel. Another option would be to simply disallow the users from entering a comma in their name. If you take that approach, however, it would be better to pick a less common character for your delimiter, such as a pipe symbol (|).
Instead of using a CSV file, I would recommend using an XML file. Then you could easily store the data, even if it grows in complexity, and read and write to it using either the XmlSerializer, XmlDocument, or XDocument.
If all you need to do is store simple values like this, though, an even better option may to be to use the My.Settings feature.