Changing item in foreach thru method - c#

Let's start with the following snippet:
Foreach(Record item in RecordList){
..
item = UpdateRecord(item, 5);
..
}
The UpdateRecode function changes some field of item and returns the altered object. In this case the compiler throws an exception saying that the item can not be updated in a foreach iteration.
Now the UpdateRecord method is changed so that it returns void and the snippet would look like this:
Foreach(Record item in RecordList){
..
UpdateRecord(item, 5);
..
}
In this case the item would be updated because Record is a reference type. But it makes the code unreadable.
The project I'm working on has lots of foreach-loops with the almost the same code over and over, so I would like to create methods that update parts of the records. Is there a nice way to do this? One that make the code more readable instead of trashing it further more?

If you need to update a collection, don't use an iterator pattern, like you said, its either error prone, or smells bad.
I find that using a for loop with an index a bit clearer in this situation, as its very obvious what you are trying to do that way.

The compiler is complaining that you can't update the collection, not the record. By doing item = UpdateRecord, you are reassigning the iterator variable item.
I disagree that UpdateRecord(item, 5) is in any way unreadable - but if it makes you feel better, an extension method may make it more clear that you are changing the contents of item.
static void Update(this Record item, int value) {
// do logic
}
foreach (Record item in RecordList) {
item.Update(5);
}

Do you need to update the same list? Could you return a new (updated) enumeration instead?
foreach(Record item in RecordList){
..
yield return GetUpdatedRecord(item, 5);
..
}

Related

Can't remove item from list in C#

In the inventory for my game I want to delete an item if you drop it. This only works in a foreach loop like this:
public List<Item> items = new List<Item>();
public void DeleteItem(Item itemToRemove)
{
foreach (var item in items)
{
items.Remove(itemToRemove);
}
}
but then I get this error:
Collection was modified; enumeration operation may not execute.
When I just try this:
public void DeleteItem(Item itemToRemove)
{
items.Remove(itemToRemove);
}
nothing happens. Does anyone know how to fix this?
To remove an item from a list, it is sufficient to do this:
public void DeleteItem(Item itemToRemove)
{
items.Remove(itemToRemove);
}
You don't actually have to loop through the list. If nothing seems to happen, check the return value of Remove. It returns true if the item was removed, or false if the item couldn't be found in the list.
Regarding the error message you're getting: you must not modify a list by adding/removing items while you're enumerating through it. This will change the length of the list, and throws off the enumeration, so it is not allowed.
That error indicates that you are changing a collection at the same time as you are looping over it. This is unsafe because once the collection changes the iterator state may no longer be valid. For example, the iterator might be pointing to index 3 in a list, but if you remove item 0 (shifting everything else back) then you’d end up skipping ahead. Throwing this error avoids this sort of subtle bug by making it obvious.
In this particular case, you shouldn’t need a loop: just remove the item.
If you want to do bulk removal, consider methods like RemoveAll.
Another option is to loop over a copy of the list, which leaves you free to modify it. For example, you could loop over items.ToArray().
There was another script attached which directly added the item back if I dropped the gun, so that was the error.

Update list while adding the new item in a list of string in foreach loop

Suppose we have a List<string> assetIds = GetAllAssetId(assetentities); called assetIds. Now suppose we have a list with 10 AssetIds in it. If you wanted to add the new AssetItems in a list of string and I need to update the list while doing the foreach loop. what would be the best way to do it?
Please have a look of the code here.
I am getting an Error :Collection was modified; enumeration operation may not execute. If I try to go for second time in foreach loop.
Yout change the collection in foreach loop so what you could do - make a copy of the collection in loop
foreach(var element in assetIds.ToList()) {
assetIds.Add(new Item());
)
collection that the foreach loop operates on is different that the one you add to
This is one way to get rid of the error. In your case I assume that there won't be any cases where the newly added item would be used twice.
Edit:
But again think if this is the solution that you want to go for. Maybe rewriting your code is better option

Remove all matching nodes in a list

There are two things wrong with my script that i need help understanding.
The if statement looks for matching values in the original list. Then removes it. This doesn't work for some reason. the items.Remove(item) doesn't actually remove the item.
The else statement works. But i have a feeling that i can do this all in one line.
Thoughts? If possible I would like to avoid making it a list. Its originally an ObservableCollection.
var items = TreeViewCollection.ToList();
if (items.Contains(SelectedTreeViewItem))
items.Remove(SelectedTreeViewItem);
else
items.ForEach(e=>e.Nodes.Remove(SelectedTreeViewItem));
UPDATE: Ive updated the code to make a little more sense.
My goal is to make this into one line.
if (TreeViewCollection.Contains(SelectedTreeViewItem))
TreeViewCollection.Remove(SelectedTreeViewItem);
else
TreeViewCollection.ToList().ForEach(e=>e.Nodes.Remove(SelectedTreeViewItem));
Creating a new list is counterproductive. SelectedTreeViewItem will be removed from list but not from original collection.
ForEach is not an extension method for IEnumerable, it is a common method of List<T> class. It returns void, so it can only be in the end of Linq methods chain. Using foreach makes code simpler and clearly states its intent:
bool del = TreeViewCollection.Remove(SelectedTreeViewItem);
if (false == del)
foreach(var t in TreeViewCollection)
t.Nodes.Remove(SelectedTreeViewItem);

Why can't I modify the loop variable in a foreach?

Why is a foreach loop a read only loop? What reasons are there for this?
I'm not sure exactly what you mean by a "readonly loop" but I'm guessing that you want to know why this doesn't compile:
int[] ints = { 1, 2, 3 };
foreach (int x in ints)
{
x = 4;
}
The above code will give the following compile error:
Cannot assign to 'x' because it is a 'foreach iteration variable'
Why is this disallowed? Trying to assigning to it probably wouldn't do what you want - it wouldn't modify the contents of the original collection. This is because the variable x is not a reference to the elements in the list - it is a copy. To avoid people writing buggy code, the compiler disallows this.
I would assume it's how the iterator travels through the list.
Say you have a sorted list:
Alaska
Nebraska
Ohio
In the middle of
foreach(var s in States)
{
}
You do a States.Add("Missouri")
How do you handle that? Do you then jump to Missouri even if you're already past that index.
If, by this, you mean:
Why shouldn't I modify the collection that's being foreach'd over?
There's no surety that the items that you're getting come out in a given order, and that adding an item, or removing an item won't cause the order of items in the collection to change, or even the Enumerator to become invalid.
Imagine if you ran the following code:
var items = GetListOfTOfSomething(); // Returns 10 items
int i = 0;
foreach(vat item in items)
{
i++;
if (i == 5)
{
items.Remove(item);
}
}
As soon as you hit the loop where i is 6 (i.e. after the item is removed) anything could happen. The Enumerator might have been invalidated due to you removing an item, everything might have "shuffled up by one" in the underlying collection causing an item to take the place of the removed one, meaning you "skip" one.
If you meant "why can't I change the value that is provided on each iteration" then, if the collection you're working with contains value types, any changes you make won't be preserved as it's a value you're working with, rather than a reference.
The foreach command uses the IEnumerable interface to loop throught the collection. The interface only defined methods for stepping through a collection and get the current item, there is no methods for updating the collection.
As the interface only defines the minimal methods required to read the collecton in one direction, the interface can be implemented by a wide range of collections.
As you only access a single item at a time, the entire collection doesn't have to exist at the same time. This is for example used by LINQ expressions, where it creates the result on the fly as you read it, instead of first creating the entire result and then let you loop through it.
Not sure what you mean with read-only but I'm guessing that understanding what the foreach loop is under the hood will help. It's syntactic sugar and could also be written something like this:
IEnumerator enumerator = list.GetEnumerator();
while(enumerator.MoveNext())
{
T element = enumerator.Current;
//body goes here
}
If you change the collection (list) it's getting hard to impossible to figure out how to process the iteration.
Assigning to element (in the foreach version) could be viewed as either trying to assign to enumerator.Current which is read only or trying to change the value of the local holding a ref to enumerator.Current in which case you might as well introduce a local yourself because it no longer has anything to do with the enumerated list anymore.
foreach works with everything implementing the IEnumerable interface. In order to avoid synchronization issues, the enumerable shall never be modified while iterating on it.
The problems arise if you add or remove items in another thread while iterating: depending on where you are you might miss an item or apply your code to an extra item. This is detected by the runtime (in some cases or all???) and throws an exception:
System.InvalidOperationException was unhandled
Message="Collection was modified; enumeration operation may not execute."
foreach tries to get next item on each iteration which can cause trouble if you are modifying it from another thread at the same time.

Enumerator problem, Any way to avoid two loops?

I have a third party api, which has a class that returns an enumerator for different items in the class.
I need to remove an item in that enumerator, so I cannot use "for each". Only option I can think of is to get the count by iterating over the enum and then run a normal for loop to remove the items.
Anyone know of a way to avoid the two loops?
Thanks
[update] sorry for the confusion but Andrey below in comments is right.
Here is some pseudo code out of my head that won't work and for which I am looking a solution which won't involve two loops but I guess it's not possible:
for each (myProperty in MyProperty)
{
if (checking some criteria here)
MyProperty.Remove(myProperty)
}
MyProperty is the third party class that implements the enumerator and the remove method.
Common pattern is to do something like this:
List<Item> forDeletion = new List<Item>();
foreach (Item i in somelist)
if (condition for deletion) forDeletion.Add(i);
foreach (Item i in forDeletion)
somelist.Remove(i); //or how do you delete items
Loop through it once and create a second array which contains the items which should not be deleted.
If you know it's a collection, you can go with reverted for:
for (int i = items.Count - 1; i >= 0; i--)
{
items.RemoveAt(i);
}
Otherwise, you'll have to do two loops.
You can create something like this:
public IEnumerable<item> GetMyList()
{
foreach (var x in thirdParty )
{
if (x == ignore)
continue;
yield return x;
}
}
I need to remove an item in that enumerator
As long as this is a single item that's not a problem. The rule is that you cannot continue to iterate after modifying the collection. Thus:
foreach (var item in collection) {
if (item.Equals(toRemove) {
collection.Remove(toRemove);
break; // <== stop iterating!!
}
}
It is not possible to remove an item from an Enumerator. What you can do is to copy or filter(or both) the content of the whole enumeration sequence.
You can achieve this by using linq and do smth like this:
YourEnumerationReturningFunction().Where(item => yourRemovalCriteria);
Can you elaborate on the API and the API calls you are using?
If you receive an IEnumerator<T> or IEnumerable<T> you cannot remove any item from the sequence behind the enumerator because there is no method to do so. And you should of course not rely on down casting an received object because the implementation may change. (Actually a well designed API should not expose mutable objects holding internal state at all.)
If you receive IList<T> or something similar you can just use a normal for loop from back to front and remove the items as needed because there is no iterator which state could be corrupted. (Here the rule about exposing mutable state should apply again - modifying the returned collection should not change any state.)
IEnumerator.Count() will decide at run-time what it needs to do - enumerate to count or reflect to see it's a collection and call .Count that way.
I like SJoerd's suggestion but I worry about how many items we may be talking about.
Why not something like ..
// you don't want 2 and 3
IEnumerable<int> fromAPI = Enumerable.Range(0, 10);
IEnumerable<int> result = fromAPI.Except(new[] { 2, 3 });
A clean, readable way to do this is as follows (I'm guessing at the third-party container's API here since you haven't specified it.)
foreach(var delItem in ThirdPartyContainer.Items
.Where(item=>ShouldIDeleteThis(item))
//or: .Where(ShouldIDeleteThis)
.ToArray()) {
ThirdPartyContainer.Remove(delItem);
}
The call to .ToArray() ensures that all items to be deleted have been greedily cached before the foreach iteration begins.
Behind the scenes this involves an array and an extra iteration over that, but that's generally very cheap, and the advantage of this method over the other answers to this question is that it works on plain enumerables and does not involve tricky mutable state issues that are hard to read and easy to get wrong.
By contrast, iterating in reverse, while not rocket science, is much more prone to off-by-one errors and harder to read; and it also relies on internals of the collection such as not changing order in between deletions (e.g. better not be a binary heap, say). Manually adding items that should be deleted to a temporary list is just unnecessary code - that's what .ToArray() will do just fine :-).
an enumerator always has a private field pointing to the real collection.
you can get it via reflection.modify it.
have fun.

Categories