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

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);
}

Related

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.

work around to replace item in foreach loop

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?

changing a value of a term while iterating a HashSet

i am using a HashSet in order to avoid having two (or more)items with the same value inside my collection , on my work i need to iterate over my hashset and remove its values but unfortunatly i cant do so , what i am trying to do is:
string newValue = "";
HashSet<string> myHashSet;
myHashSet = GetAllValues(); // lets say there is a function which fill the hashset
foreach (string s in myHashSet)
{
newValue = func(s) // lets say that func on some cases returns s as it was and
if(s != newValue) // for some cases returns another va
{
myHashSet.Remove(s);
myHashSet.Add(newValue);
}
}
thanks in advance for your kind help
You cannot modify the container while it's being iterated. The solution would be to project the initial set into a "modified" set using LINQ (Enumerable.Select), and create a new HashSet from the results of the projection.
Since if there is a func with the appropriate signature you can directly stick it into the Enumerable.Select method, and since HashSet has a constructor that accepts an IEnumerable<T>, it all comes down to one line:
var modifiedHashSet = new HashSet(myHashSet.Select(func));
The accepted answer is indeed correct, but if, as in my case, you require modification of the same instance, you can iterate through a copy of the HashSet.
foreach (string s in myHashSet.ToArray()) // ToArray will create a copy
{
newValue = func(s)
if(s != newValue)
{
myHashSet.Remove(s);
myHashSet.Add(newValue);
}
}

C# foreach on IEnumerable vs. List - element modification persistent only for array - Why?

In C#, I have noticed that if I am running a foreach loop on a LINQ generated IEnumerable<T> collection and try to modify the contents of each T element, my modifications are not persistent.
On the other hand, if I apply the ToArray() or ToList() method when creating my collection, modification of the individual elements in the foreach loop are persistent.
I suspect that this is in some way related to deferred execution, but exactly how is not entirely obvious to me. I would really appreciate an explanation to this difference in behavior.
Here is some example code - I have a class MyClass with a constructor and auto-implemented property:
public class MyClass
{
public MyClass(int val) { Str = val.ToString(); }
public string Str { get; set; }
}
In my example application I use LINQ Select() to create two collections of MyClass objects based on a collection of integers, one IEnumerable<MyClass>, and one IList<MyClass> by applying the ToList() method in the end.
var ints = Enumerable.Range(1, 10);
var myClassEnumerable = ints.Select(i => new MyClass(i));
var myClassArray = ints.Select(i => new MyClass(i)).ToList();
Next, I run a foreach loop over each of the collections, and modify the contents of the looped-over MyClass objects:
foreach (var obj in myClassEnumerable) obj.Str = "Something";
foreach (var obj in myClassArray) obj.Str = "Something else";
Finally, I output the Str member of the first element in each collection:
Console.WriteLine(myClassEnumerable.First().Str);
Console.WriteLine(myClassArray.First().Str);
Somewhat counter-intuitively, the output is:
1
Something else
Deferred execution is the indeed the key point.
Executing myClassEnumerable.First().Str will reexecute your query ints.Select(i => new MyClass(i)); and so it will give you a new IEnumerable with a new list of integers.
You can see this in action using your debugger. Put a breakpoint at the new MyClass(i) part of the IEnumerable select and you will see that this part get's hit again when you execute it for Console.WriteLine
You are right, it is deferred execution. A new MyClass instance is created each time you iterate the IEnumerable. By calling ToList or ToArray you then create a List or Array and populate it with the new MyClass instances created from the iteration of the IEnumerable.

Duplicate IEnumerable, List and Cast

after reading this very interesting thread on duplicate removal, i ended with this =>
public static IEnumerable<T> deDuplicateCollection<T>(IEnumerable<T> input)
{
var hs = new HashSet<T>();
foreach (T t in input)
if (hs.Add(t))
yield return t;
}
by the way, as i'm brand new to C# and coming from Python, i'm a bit lost between casting and this kind of thing... i was able to compile and build with :
foreach (KeyValuePair<long, List<string>> kvp in d)
{
d[kvp.Key] = (List<string>) deDuplicateCollection(kvp.Value);
}
but i must have missed something here... as i get a "System.InvalidCastException" # runtime, maybe could you point interesting things about casting and where i'm wrong? Thank you in advance.
First, about the usage of the method.
Drop the cast, invoke ToList() on the result of the method. The result of the method is IEnumerable<string>, this is not a List<string>. The fact the source is originally a List<string> is irrelevant, you don't return the list, you yield return a sequence.
d[kvp.Key] = deDuplicateCollection(kvp.Value).ToList();
Second, your deDuplicateCollection method is redundant, Distinct() already exists in the library and performs the same function.
d[kvp.Key] = kvp.Value.Distinct().ToList();
Just be sure you have a using System.Linq; in the directives so you can use these Distinct() and ToList() extension methods.
Finally, you'll notice making this change alone, you run into a new exception when trying to change the dictionary in the loop. You cannot update the collection in a foreach. The simplest way to do what you want is to omit the explicit loop entirely. Consider
d = d.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Distinct().ToList());
This uses another Linq extension method, ToDictionary(). Note: this creates a new dictionary in memory and updates d to reference it. If you need to preserve the original dictionary as referenced by d, then you would need to approach this another way. A simple option here is to build a dictionary to shadow d, and then update d with it.
var shadow = new Dictionary<string, string>();
foreach (var kvp in d)
{
shadow[kvp.Key] = kvp.Value.Distinct().ToList();
}
foreach (var kvp in shadow)
{
d[kvp.Key] = kvp.Value;
}
These two loops are safe, but you see you need to loop twice to avoid the problem of updating the original collection while enumerating over it while also preserving the original collection in memory.
d[kvp.Key] = kvp.Value.Distinct().ToList();
There is already a Distinct extension method to remove duplicates!

Categories