vb.net: index number in "for each" - vb.net

Sometime in VB.net i have something like:
For Each El in Collection
Write(El)
Next
But if i need the index number, i have to change it to
For I = 0 To Collection.Count() - 1
Write(I & " = " & Collection(I))
Next
Or even (worse)
I = 0
For Each El In Collection
Write(I & " = " & El)
I += 1
Next
Is there another way of getting the index?

If you are using a generic collection (Collection(of T)) then you can use the IndexOf method.
For Each El in Collection
Write(Collection.IndexOf(El) & " = " & El)
Next

I believe your original way of doing it with a counter variable is the most efficient way of doing it. Using Linq or IndexOf would kill the performance.
Dim i as Integer = 0
For Each obj In myList
'Do stuff
i+=1
Next

If you need the index then a for loop is the most straightforward option and has great performance. Apart from the alternatives you mentioned, you could use the overloaded Select method to keep track of the indices and continue using the foreach loop.
Dim list = Enumerable.Range(1, 10).Reverse() ''# sample list
Dim query = list.Select(Function(item, index) _
New With { .Index = index, .Item = item })
For Each obj In query
Console.WriteLine("Index: {0} -- Item: {1}", obj.Index, obj.Item)
Next
However, I would stick to the for loop if the only reason is to iterate over it and know the index. The above doesn't make it clear why you chose to skip the for loop.

If you need the index, change the way you iterate through the collection:
You have already come up with the simplest answer:
For i = 0 To Collection.Count() - 1
DoStuffWith(Collection(i))
Next

Related

Why is my loop through a form control array stopping one short?

I have done a for each loop on form controls before. I don't think it's complicated code. I've used the same method I'm using now, but for some reason the loop is stopping one short.
One thing to note is that my controls are made thusly:
For i As Integer = 1 To sheetCount
Dim wb As New WebBrowser
With wb
.Name = "pqCheck" & i
.Navigate(New Uri(savePath & "PQ Check Sheet " & i & ".pdf"))
.Location = New Point(((i - 1) * (screenWidth / sheetCount)), browserHeight)
.Size = New Size(screenWidth / sheetCount, screenHeight - browserHeight)
.ScrollBarsEnabled = False
form.Controls.Add(wb)
End With
Next
I do this for reasons. But I'm curious to know if creating controls this way somehow confuses my loop to think all the controls made this way share some sort of identifier. (Should note that I make a lot of controls this way in my projects, and have been able to successfully loop through each control before without issue.)
Dim cnt As Integer = 0
Debug.Print("Controls: " & frmMain.Controls.Count)
For Each cControl In frmMain.Controls
If TypeOf cControl Is WebBrowser Then
If cControl.Name.Contains("pqCheck") Then
cControl.Dispose()
End If
cnt += cnt
Debug.Print("Loop Count: " & cnt)
End If
Next
Debug outputs:
Controls: 3
Loop Count: 1
Loop Count: 2
No loop count 3. I am confused.
You should copy the Controls collection into an array before disposing of the WebBrower controls
Dim arr(frmMain.Controls.Count) As Control
Controls.CopyTo(arr, 0)
Dim cnt As Integer = 0
Debug.Print("Controls: " & frmMain.Controls.Count)
For Each cControl In arr
If TypeOf cControl Is WebBrowser Then
If cControl.Name.Contains("pqCheck") Then
cControl.Dispose()
End If
cnt += cnt
Debug.Print("Loop Count: " & cnt)
End If
Next
You need to create a separate list of the references because Dispose also will remove the control from the collection, changing the index and messing up the foreach statement
Special shout-out to user2818985 for pointing me in the right direction. It all came down to the index of the controls. When you dispose of a control, it will adjust the index and screw things up a bit (a lot).
To solve this, simply reverse the loop. (I'm sure I can fine tune this, but it works, so ya us! Go team and all that.)
For i As Integer = frmMain.Controls.Count To 0 Step -1
For Each cControl In frmMain.Controls
If TypeOf cControl Is WebBrowser Then
If cControl.Name.Contains("pqCheck") Then
cControl.Dispose()
End If
cnt += 1
Else
Debug.Print("Else Control: " & cControl.Name)
End If
Debug.Print("Loop Count: " & cnt)
Next
Next

Listbox Remove Spaces

I am trying to use this code to remove spaces from a listbox but it is not working
Dim word As String() = {" "}
For i As Integer = 0 To ListBox5.Items.Count - 1
For Each Word As String In word
If ListBox5.Items(i).ToString.Contains(Word) Then
ListBox5.Items(i) = ListBox5.Items(i).ToString.Replace(Word, String.Empty)
End If
Next
Next
any help would be appreciated a lot.
You define a string array with one value and its static, why?
It seems you could do this simply by coding it like this
scan every item and replace " " with string.empty,
don't bother checking if it exists, just run the replace statement on every item
For i As Integer = 0 To ListBox5.Items.Count - 1
ListBox5.Items(i) = ListBox5.Items(i).ToString.Replace(" ", String.Empty)
Next
try this code
For i As Integer = 0 To ListBox5.Items.Count - 1
ListBox5.Items(i) = ListBox5.Items(i).ToString.Replace(" ", Nothing)
Next
fist we have to loop through the listbox items. we declared the items from index 0 to the final item index,ie listbox.items.count-1 . this is stored in a variable i. next all the items ,say from 0 to count-1 is replaced.
istBox5.Items(i).ToString.Replace(" ", Nothing) " " indicates a space and'nothing' indicates null.you can also use regex.replace here by importing system.regularexpressions.

For Each Loop Iteration Count

Can we keep track of our iteration in our loop when we use a For Each? I like to use the For Each loops for looping through my objects but I cannot seem to find a way to keep an index of where I'm at in the loop. Unless of course I create my own ...
If you want to have an index, use a For-loop instead of a For each. That's why it exists.
For i As Int32 = 0 To objects.Count - 1
Dim obj = objects(i)
Next
Of course nothing prevents you from creating your own counter:
Dim counter As Int32 = 0
For Each obj In objects
counter += 1
Next
or you can use Linq:
Dim query = objects.Select(Function(obj, index) "Index is:" & index)
There's no built-in solution yet. Of course you know how to do it with a counter but this maybe what you are looking for (By Jon Skeet)
You can do it like this:
Dim index as integer = 0
For Each item in list
'do stuff
index += 1
Next
Off course, depending on the collection your iterating over, there is no guaranteeing that item will be the same as list.item(index), but wether or not that matters depends on what you are doing.
For index as integer = 0 to list.count - 1
Dim item = list.item(index)
'do stuff
Next
That is the other alternative if you need item to be the same as list.item(index).

Visual Basic Loop and Display one line at a time

I'm using visual studios 2008, VB9 and I am trying to write an app that basically performs calculations on a set of data input by a user. During the calculations, I want to display the data at each step, and have it retained in the display area on the GUI (not overwritten by the next data being displayed).
For example:
UserInput = 1
Do
UserInput += 1
OutputLabel.Text = "UserInput " & UserInput
Loop Until UserInput = 5
and the output would look like
UserInput 1
UserInput 2
UserInput 3
UserInput 4
UserInput 5
I tried this, and other loop structures and can't seem to get things right. The actual app is a bit more sophisticated, but the example serves well for logical purposes.
Any tips are welcome, thanks!
This is the simple version:
Dim delimiter as String = ""
For UserInput As Integer = 1 To 5
OutputLabel.Text &= String.Format("{0}UserInput {1}", delimiter, UserInput)
delimiter = " "
Next
However, there are two problems with it and others like it (including every other answer given so far):
It creates a lot of extra strings
Since it's in a loop the label won't be able to process any paint events to update itself until you finish all of your processing.
So you may as well just do this:
Dim sb As New StringBuilder()
Dim delimiter As String = ""
For UserInput As Integer = 1 To 5
sb.AppendFormat("{0}UserInput {1}", delimiter, UserInput)
delimiter = " "
Next
OutputLabel.Text = sb.ToString()
And if you really want to have fun you can just do something like this (no loop required!):
OutputLabel.Text = Enumerable.Range(1, 5).Aggregate(Of String)("", Function(s, i) s & String.Format("UserInput {0} ", i))
You need to concatenate the value in OutputLabel.Text.
OutputLabel.Text &= "UserInput " & UserInput
You might also want to reset it before the loop: OutputLabel.Text = ""
If you need an iterated index you can try something like the following
For I As Integer = 1 To 5
If I > 1 Then OutputLabel.Text &= " "
OutputLabel.Text &= "UserInput " & I.ToString()
End For
If you have user inputs in a collection, you might better be served by using ForEach loop.
Do you need to do it in a GUI? If it is simply processing and putting out rows like that, maybe you should consider a console application, in which case it becomes REALLY easy, in simply calling
Console.WriteLine("my string")
All of these ways actually work really well but the one that fit my situation the best was this:
Do
Dim OutputString as String
Application.DoEvents() 'to make things paint actively
UserInput += 1
OutputString = String.Format("{0}", UserInput)
ListBox.Items.Add(OutputString)
Loop Until UserInput = 5
I changed things to a listbox but tried this same method with textboxes and labels, with some tweaks, they all worked very well. Thanks for all your help!
I'd use a more appropriate control, like richtextbox
Dim UserInput As Integer = 0
Const userDone As Integer = 5
RichTextBox1.Clear()
Do
RichTextBox1.AppendText(String.Format("User input {0:n0} ", UserInput))
RichTextBox1.AppendText(Environment.NewLine)
RichTextBox1.Refresh() 'the data at each step
UserInput += 1
Loop Until UserInput = userDone

A better way of writing this : growing array

Was looking at some code earlier, and am thinking that there has to be a more elegant way of writing this....
(returnVar.Warnings is a string array, it could be returned as any size depending on the number of warnings that are logged)
For Each item In items
If o.ImageContent.ImageId = 0 Then
ReDim Preserve returnVar.Warnings(returnVar.Warnings.GetUpperBound(0) + 1)
returnVar.Warnings(returnVar.Warnings.GetUpperBound(0)) = "Section: " & section.<header>.<title>.ToString & " , Item: " & item.<title>.ToString
End If
Next
use the generic List(of string) then get an array containing the list data if you need it
dim list = new List(of string)
list.Add("foo")
list.Add("bar")
list.ToArray()
Can't you use ArrayList which does this for you?
http://msdn.microsoft.com/en-us/library/system.collections.arraylist.aspx
Start by moving the If statement out of the loop.
If you are using framework 3.5, you can use LINQ to loop the items.
If o.ImageContent.ImageId = 0 Then
returnVar.Warnings = items.Select(Function(item) "Section: " & section.<header>.<title>.ToString & " , Item: " & item.<title>.ToString).ToArray()
Else
returnVar.Warnings = New String() {}
End If