Removing controls in a loop results in a strange behaviour - c#

I'm having some weird behaviour when I try to remove all my dynamically allocated buttons.
I give each of these buttons the same name as in "dynamicButton"
for (int i = 0; i < 10; i++)
{
foreach (Control item in Controls.OfType<Control>())
{
if (item.Name.Contains("dynamicButton"))
{
Controls.Remove(item);
}
}
}
Notice the that it loops this 10 times (though 6 or 7 would be enough).
Below I'll show you how it does things.
At i=0
At i=1
At i=2
At i=3
And so on until they all dissapear.
As shown, I'm making a lot of unecessary loops inside this thing, and for some reason it doesn't take the all out even though I'm using the same parameter going in...
Anyone has any idea why this is happening?

You're modifying the collection while looping over it. Some collection types (like List<T>) have built-in "protection" against this, throwing an exception on the next iteration, but ControlCollection apparently doesn't.
Say you're at element #4 and you remove it - element #5 will then move to #4, causing it to be skipped.
You could reverse the loop, ie:
for (int i = Controls.Count - 1; i >= 0; i--)
{
Control item = Controls[i];
if (item.Name.Contains("dynamicButton"))
{
Controls.RemoveAt(i);
}
}

Related

Pre-decrement of value in a for loop is not correctly decreasing value on first loop

I have the following for loop in my code (using C#)
for (int i = 150; i >= 75; --i)
{
print("I holds " + i);
images[i].gameObject.SetActive(false);
}
trying to run through each item in a list, and disable the object. The list holds 150 objects (but since it starts at value zero, the final reference position is 149)
So, I figured a for loop was a good way to iterate through them all. Yet, I try to decrease the value of i to 149 for the first run of the loop, but it still passes a value of 150 into the loop in the first run, which throws the expected ("Argument is out of range") error.
Can anyone work out why the decreased value isn't correctly being passed to the loop?
I tried both decreasing it before and after the first run of the loop, but both times it passes a value of 150 into the loop.
I feel this should be a relatively simple issue to solve, yet it's not working as I expected it to do!
for (int i = 10; i >= 0; --i)
is the same as
for (int i = 10; i >= 0; i--)
i does not decrease/increase on the first loop. This is for many languages. Just start with 149 and it works.
Answer for "Can anyone work out why the decreased value isn't correctly being passed to the loop?"
Another way to loop through all items of an array without caring of actual indices is to make use of a foreach statement:
foreach(var image in images)
{
image.gameObject.SetActive(false);
}
If you want to use a for statement. I would suggest you write it as below:
for(var i=0; i<images.Length; i++)
{
image[i].gameObject.SetActive(false);
}
Doing so, you are pretty confident that you are not going to be out of the array's size. You start at the element at the position with index of 0 and you read the last item stored in the array, in the position of images.Length-1.
Update
If you want to update only the first 75 items (where 75 is half the total items in the array) in your array you could try this:
for(var i=0; i<images.Length/2; i++)
{
image[i].gameObject.SetActive(false);
}

While debugging, how can I start a foreach loop at an arbitrary position in the enumeration?

I am debugging my program. Can I set a start object for the foreach loop in the debug mode? For instance, I want the foreach loop to start from the 5th element of my collection.
No, you cant. Foreach loop uses IEnumerable<T> where T is the type of object. So unlike for loop you cannot set initial or start index for the iteration. So you will always start from the 0th location object.
Other option is to use linq as shown below.
//Add using System.Linq statement at top.
int[] numbers = new int[] { 1,2,3,4,5,6,7,8};
foreach(var num in numbers.Skip(5))
{
Console.WriteLine(num);
}
Considering you are talking about debugging I'm assuming what you want to do is to break from the 5th element on etc. If this is the case then you can use a break point specifying a hit count which will break on the nth hit and so on.
See MSDN here.
An easy way to set the start object is to overwrite the variable that you are enumerating in the loop.
To illustrate, consider the following foreach loop.
IEnumerable<int> numbers = Enumerable.Range(1, 100);
foreach (int n in numbers)
{
Console.WriteLine(n);
}
Set your breakpoint on the numbers statement in the loop initializer, or step to the loop but don't run the numbers statement.
Use the immediate window to override the value of numbers:
numbers = numbers.Skip(5); // or Enumerable.Skip(numbers, 5)
Continue debugging; loop runs from the sixth element.
If your loop uses an inline-computed enumeration as follows, then you're out of luck. Consider using a hit-count breakpoint instead.
foreach (int n in Enumerable.Range(1, 100)) // no way to change enumeration.
{
Console.WriteLine(n);
}
Note: once the numbers statement is run, the debugger will cache your enumeration so it may no longer be changed and affect the loop. You may observe this while stepping through the loop construct.
foreach does not necessarily operate on a collection which does even have a defined order. consider a Dictionary<T> or a ConcurrentBag<T>.
so, no, this is not possible in the general case, especially without code changes (which could change the problem you are trying to debug).
#Shaggy's answer is probably the easiest, but it's good to know that the old fashioned for loop is the fastest, and enables you to skip the 0 index.
for(int x = 4; x < numbers.Length; x++)
{
// Do anything with numbers[x]
}
Even though I would not recommend it (It's really bad code), you could make this loop behave different when debugging, as you asked:
#if DEBUG
for(int x = 4; x < numbers.Length; x++)
#else
for(int x = 0; x < numbers.Length; x++)
#endif
{
// Do anything with numbers[x]
}

How does List.RemoveAt(index) work?

Let's say I have a list, messages, with three items. I wan't to loop through them and remove one item at a time.
for (int i = 0; i < messages.Count; i++)
{
messages.RemoveAt(i);
}
(I've removed lots of irrelevant code)
What happen to the remaining messages after the first iteration? Are they moved to another index or can I do it like this to remove all three messages?
Thank you
The index of all elements behind the index you remove will be decremented.
If you want to avoid this with your loop let it run in reverse (delete from highest index to lowest).
for (int i = messages.Count - 1; i >= 0; i--)
{
messages.RemoveAt(i);
}
or just use
messages.Clear()
to delete all elements at once without taking care about any indices.
If you just want to clear the List it's also more efficient to use Clear since it is a O(n) operation. RemoveAt is O(n) as well but inside another O(n) loop which makes it O(n^2) - not that it would matter with 3 elements as mentionend in your example but when talking about larger lists it would certainly make a difference.
In your code, it's simpler to just call messages.Clear();. There's no need to remove each element separately.
Your code will skip every other element as it removes them until the for loop's conditional is no longer met. It will remove the elements at indexes 0 and 2 because you said your collection has three elements.
Let's step through your algorithm:
Initially, the list has three items, listed with their indexes: 0: "Hello", 1: "World", and 2: "Foo".
Your loop removes the element at index 0. The list now looks like this:
0: "World", 1: "Foo"
However, your loop executes again, since i now equals 1 and 1 < 2. The element at index 1 is then removed:
0: "World"
i is incremented to 2 and the conditional is no longer met (i is not less than 1). Your list now consists of what used to be the second element.
You need to iterate backward
for (int i = messages.Count - 1; i >=0; i--)
{
messages.RemoveAt(i);
}
Because in your current loop, you will be left with one time, if your list contains 3 items.
If you want to remove all items from your list then there is a method List<T>.RemoveAll Method
They're moved, see MSDN on List<T>.RemoveAt method:
When you call RemoveAt to remove an item, the remaining items in the
list are renumbered to replace the removed item. For example, if you
remove the item at index 3, the item at index 4 is moved to the 3
position.
To remove all elements, the Clear method is more suitable.
Go thru reverse loop..
for(int i = messages.Count - 1; i >= 0 ; i--) {
messages.RemoveAt(i);
}
You could just changes it to always delete the first one
List<string> messages = new List<string>();
messages.Add("a");
messages.Add("b");
messages.Add("c");
for (int i = 0; i < messages.Count; i++)
{
messages.RemoveAt(0);
}
or to clear whole list in one statement
messages.Clear()
.NET Reference Source has following definition of RemoveAt method:
public void RemoveAt(int index)
{
if ((uint)index >= (uint)_size)
ThrowHelper.ThrowArgumentOutOfRangeException();
Contract.EndContractBlock();
_size--;
if (index < _size)
Array.Copy(_items, index + 1, _items, index, _size - index);
_items[_size] = default(T);
_version++;
}
As you can see - if you remove item which is not last one copying of array items occurs (all items from index + 1 till the end are moved). So in your case its better to remove items from the end to avoid array copying on each iteration:
for (int i = messages.Count - 1; i >= 0; i--)
{
messages.RemoveAt(i);
}
Or simply call messages.Clear() if you want to remove them all without additional logic - in that case internal array just cleared and size set to zero.
Like the other posts, you need to iterate backward
You've got many solutions to remove the items
messages.Clear();
or
while(messages.Count != 0){
message.RemoveAt(0);
}

Looping and changing a list - remove doesn't always work

I'm trying to go through a loop 40 times and changing a list in the process.
This is the code:
for (int i = 0; i < 40; i++)
{
location = rand.Next(rows.Count);
rank = rand2.Next(pondRanks.Count);
ComputerPonds[rows[location]].Rank = (PondRank)pondRanks[rank];
rows.Remove(location);
pondRanks.Remove(rank);
}
For some reason the remove doesn't happen all the time, and only sometimes. Anyone has a suggestion?
Both of the list are List , they have 40 elements, and I want to remove the element itself.
Even when debugging I can see that the list count isn't the same (they both have the same initial numbers and they both need to do remove at this loop). If it matters, I'm working on windows phone platform..
I'm pretty sure you should be using List.RemoveAt not List.Remove. RemoveAt will remove the item at the specified index, whereas Remove will look for that object you passed in and remove it from the List if it's in there. But I'm pretty sure that looking at your code that location and rank represent the index, not the objects themselves.
for (int i = 0; i < 39; i++)
{
location = rand.Next(rows.Count);
rank = rand2.Next(pondRanks.Count);
ComputerPonds[location].Rank = (PondRank)pondRanks[rank];
rows.RemoveAt(location);
pondRanks.RemoveAt(rank);
}
EDIT: You may also want to consider making sure that your rows and pondRanks have enough elements (39) before starting the loop (or altering the i < 39 to max out at the upper limit of their length)

Clearing a list using a for loop

I'm making a Black Jack game, and at the start of every new round I need to clear the list of cards that represents the Player's and the Dealer's hands. I used this to do so:
public void ClearPlayerHand()
{
for (int i = 0; i < PlayerHand.Count; ++i)
{
PlayerHand.Remove(PlayerHand[i]);
}
}
Problem is I always seem to be left with one card left in the list, or I receive an out of bounds error, no matter how I change the value of i, what is the best method of removing all the elements from the PlayerHand?
If your collection PlayerHand implements ICollection<T> you can just call the .Clear() method.
A common implementation of this interface is List<T>.
If you do want to clear a List<T> via a for loop, you should use a reverse for loop. The reason for this is that as you remove an item from the list, it will shift all the index's down one, and you could easy run into index out of bounds exceptions.
An example of this would be:
for (int i = PlayerHand.Count - 1; i >= 0; i--)
{
PlayerHand.RemoveAt(i);
}
The other answers are right: use Clear.
But, if you wanted to do this with a loop and Remove calls, here's how you would do it:
for(int i = PlayerHand.Count - 1; i >= 0; i--)
{
PlayerHand.RemoveAt(i);
}
Reversing the direction of the iteration is the real trick.
This is the best/easiest way to do it.
PlayerHand.Clear();
Reason for out of bounds
As for why you are receiving the out of bounds exception, it's happening because you're removing elements from the list but continually counting up. You would want the last operation to remove i = 0 but it keeps counting.
Say PlayerHand has 3 items in it, the following occurs:
i = 0
remove PlayerHand[0] (it now contains 2 elements)
i = 1
remove PlayerHand[1] (it now contains 1 element)
i = 2
remove PlayerHand[2] (this throws an exception as only PlayerHand[0] exists)
Normally you would count backwards in this case:
for (int i = PlayerHand.Count - 1; i >= 0; i--)
Alternatively, you can consider using data binding and then you should update the ItemSource, instead of directly manipulating the listbox or listview items.
List<T> SomeSource=...
PlayHand.ItemSource=SomeSource;
SomeSource.Clear();
Another suggested approach beside Clear method, you can also use RemoveAll to either remove all or part of list
// Remove all items
PlayerHand.RemoveAll(x => true);
// Remove part of list
PlayerHand.RemoveAll(x => ConditionMethod(x));

Categories