work around to replace item in foreach loop - c#

In my example code below i would like to replace the item in the dictionary with a new item or assign new values to that item. How can i do this?
This is my code:
dynamic data = jss.Deserialize<dynamic>(jsonText);
foreach (KeyValuePair<string,object> item in data["typeData"])
{
if(item.Key == "somevalue")
{
item = new KeyValuePair<string,object>();
}
}
I'm getting:
Cannot assign to 'item' because it is a 'foreach iteration variable'
There must be a work-around.

foreach are considered to be read only contexts.
Don't use foreach as the message says, convert the loop to a regular for loop.
From MSDN:
This error occurs when an assignment to variable occurs in a read-
only context. Read-only contexts include foreach iteration variables,
using variables, and fixed variables. To resolve this error, avoid
assignments to a statement variable in using blocks, foreach
statements, and fixed statements.
In your case the object item is not a reference its simply a copy hence any change you make to it will not result in a change in the original object.

Depends on what you want. Do you just need to override the value? I assume so because replacing the key and value would be a very different operation (remove one item and insert another)
just iterate over the keys instead of the collection (Assuming it's a dictionary):
dynamic data = jss.Deserialize<dynamic>(jsonText)["typeData"];
foreach (string key in data.Keys)
{
if(key == "somevalue")
{
data[key] = ...;
}
}
if there's no keys property you can substitute that part with (assuming that at least there's an indexer)
foreach (string key in data.Select(pair=>pair.Key)){
...
}

The problem with your code is that you are attempting to change the value of a variable that is used as a placeholder. The variable "item" simply has the same reference that exists in the dictionary; changing the object that "item" references won't actually change anything in the Dictionary itself, and on top of that it can screw up the logic of looping through the Dictionary.
In addition to not being able to reassign the placeholder, you are not allowed to add or remove items from the Dictionary within a foreach loop that uses said Dictionary, because that will also mess up the logic of iterating through the Dictionary's items (the item that is the "current" item of the enumerator behind the scenes now no longer exists, so the enumerator may lose its place in the collection and not be able to continue.
The workaround is to enumerate a different collection when you change the original collection. Basically, a task like this requires two passes; first collect the items you want to change, then enumerate through THAT collection and make the change to the original collection:
...
var itemsToChange = new List<KeyValuePair<string, object>>();
foreach (var item in data["typeData"])
{
if(item.Key == "somevalue")
itemsToChange.Add(item);
}
foreach(var item in itemsToChange)
{
//even here you can't just "swap out" KVPs;
//you must remove the old and add the new
data["typeData"].Remove(item);
data["typeData"].Add(someNewString, someNewObject);
}

You have to either use a for loop or store the variables you want changed and change them outside of the foreach loop.

Perhaps there's something missing from your question, but it seems that the workaround is to avoid looping entirely:
dynamic data = jss.Deserialize<dynamic>(jsonText);
var item = new KeyValuePair<string, object>("somevalue", data["somevalue"]);
or perhaps:
dynamic data = jss.Deserialize<dynamic>(jsonText);
DoSomethingWith(data["somevalue"]);
what's the reason for your loop?

Related

Deserialized jsonObject gets updated, foreach then fails

I have a json File that I deserialize with Newtonssoft Json.Net like this:
/* Get current config */
dynamic json = JsonConvert.DeserializeObject<Speaker>(
File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "cfg\\speaker.json"));
dynamic jsonDevice = json.DeviceList;
/* Go through the List */
foreach (Tapi tapi in lvTapiSonos.Items)
{
foreach (var line in jsonDevice)
{
foreach (var l in line)
{
/* If not in List already, add it */
if (l.Key != tapi.Name)
{
/* Add to Config */
json.DeviceList.Add(new Dictionary<string, List<Device>>
{
{
tapi.Name,
new List<Device>
{
new Device
{
Volume = "5",
Ip = currentEditIp,
Name = currentEditName
}
}
}
});
}
}
}
}
string output = JsonConvert.SerializeObject(json, Formatting.Indented);
File.WriteAllText(AppDomain.CurrentDomain.BaseDirectory + "cfg\\speaker.json", output);
Unfortunately that only works for the first foreach as I get an exception "The List has changed. Enumeration cannot be continued," (similar as I have it in german) in the line foreach (var line in jsonDevice).
I understand this means, that the jsonDevice has been updated (it shows now one more item in debug), but since I assigned jsonDevice outside of the foreach, how is it updated? Having foreach var line in json.DeviceList produce and error seems logical as I update the json Object inside the foreach, but why does this still happen?
Any hint appreciated...
The foreach statement is used to iterate through the collection to get
the information that you want, but can not be used to add or remove
items from the source collection to avoid unpredictable side effects.
If you need to add or remove items from the source collection, use a
for loop.
http://msdn.microsoft.com/en-us/library/ttw7t8t6.aspx
As explained you can use for loop for your usecase. but if you call ToList() or ToArray you can get copy of items which can be use for iterate
dynamic jsonDevice = json.DeviceList.ToList();
Following is the ilegal code here:
json.DeviceList.Add(new Dictionary<string, List<Device>>
Why ?
In foreach you get an enumerator to the collection, which is read-only forward moving, you cannot change the original source inside the foreach loop. You are accessing the jsonDevice, which is a reference copy of json.DeviceList collection which is modified
What to do:
Convert to for loop and access by index, then you can change any collection as you are accessing by index and not changing the original collection with an attached enumerator
Or
Create a deep copy of the collection, creating a new object and copying all value members and deep copy for all reference type. You may override Memberwiseclone of the base object class

Iterating a list of objects with foreach

I came across this statement:
"When using foreach on a list of objects, the iterated object instance is not editable, but the object properties are editable"
Could someone demonstrate the above with a simple example, please?
Let me re-phrase (as I found the statement in two versions), maybe this statement is more clear:
"When using foreach on a list of elements, the iteration variable that provides the element is readonly, but the element properties are editable"
foreach(var foo in foos)
{
foo = null; // WRONG, foo is not editable
foo.name = "John"; // RIGHT, foo properties are editable
}
What is means is that the items in the list can't change whilst iterating, but the contents of the items may.
this will alter the collection and prevent the foreach completing:
foreach(var item in collection)
{
collection.Remove(item);
}
This will change an item in the list and not prevent the foreach completing:
foreach(var item in collection)
{
item.name = "Neil";
}
Yes
Foreach (n in list) if (n.something==true) list.Remove(n);
this will fail
you cannot remove an item in list, unlike say a for loop
Not sure you need an example for this. You will step over each object in the collection, and you can do what you like to each of those objects, but you can't make changes to the collection itself, e.g. Insert, Remove, Clear, etc. Attempting such will throw an exception.
foreach var car in cars
{
//you can edit car.color here
//you cannot edit car
}

How to edit an iterator within a foreach loop

Scenario
I have a system that holds races, each race has a unique list of members on that race. (the list is a List< T > )
I want users to be able to remove a member (if they ARE this member) from the list of members on that race.
Problem
I'm trying to get the following code to work:
foreach (string item in hillracing.searchRaces(RaceID).RaceList) // Loop through List with foreach.
{
if (item == SelectedItem)
{
item = null;
}
}
I can't edit the variable because it is in a foreach loop, how would I achieve this another way?
You can just store it and remove it form the collection afterwards.
var toRemove = null;
foreach (string item in hillracing.searchRaces(RaceID).RaceList) // Loop through List with foreach.
{
if (item == SelectedItem)
{
toRemove = item;
break; //Can break here if you're sure there's only one SelectedItem
}
}
hillracing.searchRaces(RaceID).Racelist.Remove(toRemove);
though in this case you could also just use hillracing.searchRaces(RaceID).Racelist.Remove(SelectedItem); and you won't use the foreach loop at all.
You can't modify collection that you are looping using foreach loop. The collection used in foreach is immutable. This is by design.
The foreach statement is used to iterate through the collection to get
the information that you want, but can not be used to add or remove
items from the source collection to avoid unpredictable side effects.
If you need to add or remove items from the source collection, use a
for loop.
Using Linq you shouldn't need to loop to find the entry you want to nullify...
// Use of Single() here assumes the object definitely exists.
// Use SingleOrDefaul() if there is a chance it might not exist.
var item = hillracing.searchRaces(RaceID)
.RaceList
.Where(x => x.Item == SelectedItem).Single();
item = null;
Edit: Since you've changed the requirement to remove the item from the list, I think you'd just call the Remove method`with the found item. So the code becomes
// Use of Single() here assumes the object definitely exists.
// Use SingleOrDefaul() if there is a chance it might not exist.
var item = hillracing.searchRaces(RaceID)
.RaceList
.Where(x => x.Item == SelectedItem).Single();
hillracing.searchRaces(RaceID).RaceList.Remove(item);
You can't do that in a foreach loop. If it's an IList/IList<T> which allows random access, like an array or list, you can use a for-loop:
List<string> = hillracing.searchRaces(RaceID).RaceList;
for(int i = 0; i < list.Count; i++)
{
if(list[i] == SelectedItem)
list[i] = null;
}
So you can't add or remove items in the foreach but you also can't replace the reference. The object refers to the original value so you could modify the object(if strings werent immutable) but you can't replace the reference itself in a foreach. This is related.
Use the existing Remove()-method to search and remove the item for you:
hillracing.searchRaces(RaceID).RaceList.Remove(SelectedItem);

How add or remove object while iterating Collection in C#

I am trying to remove object while I am iterating through Collection. But I am getting exception. How can I achieve this?
Here is my code :
foreach (var gem in gems)
{
gem.Value.Update(gameTime);
if (gem.Value.BoundingCircle.Intersects(Player.BoundingRectangle))
{
gems.Remove(gem.Key); // I can't do this here, then How can I do?
OnGemCollected(gem.Value, Player);
}
}
foreach is designed for iterating over a collection without modifing it.
To remove items from a collection while iterating over it use a for loop from the end to the start of it.
for(int i = gems.Count - 1; i >=0 ; i--)
{
gems[i].Value.Update(gameTime);
if (gems[i].Value.BoundingCircle.Intersects(Player.BoundingRectangle))
{
Gem gem = gems[i];
gems.RemoveAt(i); // Assuming it's a List<Gem>
OnGemCollected(gem.Value, Player);
}
}
If it's a dictionary<string, Gem> for example, you could iterate like this:
foreach(string s in gems.Keys.ToList())
{
if(gems[s].BoundingCircle.Intersects(Player.BoundingRectangle))
{
gems.Remove(s);
}
}
The easiest way is to do what #IV4 suggested:
foreach (var gem in gems.ToList())
The ToList() will convert the Dictionary to a list of KeyValuePair, so it will work fine.
The only time you wouldn't want to do it that way is if you have a big dictionary from which you are only removing relatively few items and you want to reduce memory use.
Only in that case would you want to use one of the following approaches:
Make a list of the keys as you find them, then have a separate loop to remove the items:
List<KeyType> keysToRemove = new List<KeyType>();
foreach (var gem in gems)
{
gem.Value.Update(gameTime);
if (gem.Value.BoundingCircle.Intersects(Player.BoundingRectangle))
{
OnGemCollected(gem.Value, Player);
keysToRemove.Add(gem.Key);
}
}
foreach (var key in keysToRemove)
gems.Remove(key);
(Where KeyType is the type of key you're using. Substitute the correct type!)
Alternatively, if it is important that the gem is removed before calling OnGemCollected(), then (with key type TKey and value type TValue) do it like this:
var itemsToRemove = new List<KeyValuePair<TKey, TValue>>();
foreach (var gem in gems)
{
gem.Value.Update(gameTime);
if (gem.Value.BoundingCircle.Intersects(Player.BoundingRectangle))
itemsToRemove.Add(gem);
}
foreach (var item in itemsToRemove)
{
gems.Remove(item.Key);
OnGemCollected(item.Value, Player);
}
As the other answers say, a foreach is designed purely for iterating over a collection without modifying it as per the documenation:
The foreach statement is used to iterate through the collection to get
the desired information, but should not be used to change the contents
of the collection to avoid unpredictable side effects.
in order to do this you would need to use a for loop (storing the items of the collection you need to remove) and remove them from the collection afterwards.
However if you are using a List<T> you could do this:
lines.RemoveAll(line => line.FullfilsCertainConditions());
After going through all the answers, and being equally good. I faced a challenge where I had to modify a List and what I ended up doing worked quite well for me. So just in case anyone finds it useful. Can someone provide me feedback on how efficient it might be.
Action removeFromList;
foreach(var value in listOfValues){
if(whatever condition to remove is){
removeFromList+=()=>listOfValues.remove(value);
}
}
removeFromList?.Invoke();
removeFromList = null;
You should use the for loop instead of the foreach loop. Please refer here
Collections support foreach statement using Enumarator. Enumerators can be used to read the data in the collection, but they cannot be used to modify the underlying collection. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and the next call to MoveNext or Reset throws an InvalidOperationException.
Use for loop for collection modifying.

Why is Dictionary.Add overwriting all items in my dictionary?

I have a dictionary of type Dictionary<string, IEnumerable<string>> and a list of string values. For some reason, every time I do an Add, every value in the dictionary is overwritten. I'm completely stumped as to why this is happening. I made sure it's not a reference problem be declaring and initializing the IEnumberable object within the loop so that it's scope does not go outside one iteration, and it still does it. Here is my code:
foreach (string type in typelist)
{
IEnumerable<string> lst =
from row in root.Descendants()
where row.Attribute("serial").Value.Substring(0, 3).Equals(type)
select row.Attribute("serial").Value.Substring(3).ToLower();
serialLists.Add(type, lst);
}
where typelist is an IEnumerable<string>, root is an XElement, and serialLists is my Dictionary.
This is a captured iterator problem.
Try:
foreach (string tmp in typelist)
{
string type = tmp;
(and the rest unchanged)
Alternatively, I would evaluate the expression during the add, I.e. do a .ToList() in the .Add:
    serialLists.Add(type, lst.ToList());
The second option is probably more effective overall, although it does force evaluation of thigs that might otherwise never be needed.
The reason is that your IEnumerable<string> sequences are not being populated eagerly, but on-demand, after the foreach loop would have completed all its iterations. Thus, when any IEnumerable<string> sequence is enumerated, the type variable would always have the value of the last element in typelist.
Here is one easy way to fix it:
foreach (string type in typelist)
{
string typeCaptured = type;
IEnumerable<string> lst =
from row in root.Descendants()
where row.Attribute("serial").Value.Substring(0, 3).Equals(typeCaptured)
select row.Attribute("serial").Value.Substring(3).ToLower();
serialLists.Add(typeCaptured, lst);
}

Categories