In C #, how does foreach work when an enum container is modified
This seems like it needs to be answered, but the potential cheats I found were asking different things ...
I noticed it works fine ( sourceDirInclusion
is simple Dictionary<X,Y>
)
foreach (string dir in sourceDirInclusion.Keys)
{
if (sourceDirInclusion[dir] == null)
sourceDirInclusion.Remove(dir);
}
Does this mean that it is foreach
safe to remove items from the collection or am I lucky?
How about if I was adding more items to the dictionary rather than removing it?
The problem I'm trying to solve is that sourceDirInclusion
it is initially populated, but then each value can deposit new words into the dictionary in a second pass. For example, what I want to do is:
foreach (string dir in sourceDirInclusion.Keys)
{
X x = sourceDirInclusion[dir];
sourceDirInclusion.Add(X.dir,X.val);
}
source to share
Short answer: it is not safe.
Long answer: from the IEnumerator<T>
documentation :
The enumerator remains in effect as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or removing elements, the enumerator is irrevocably invalidated and its behavior is undefined.
Note that the docs state that the behavior is undefined, which means it may or may not work. You should never rely on undefined behavior.
In this case, it depends on the behavior of the enumerated type Keys
, regardless of whether it creates a copy of the list of keys when the enumeration starts. In this particular case , it is known from the docs that the return value from Dictionary<,>.Keys
is a collection that refers to a dictionary:
The one returned is
Dictionary<TKey, TValue>.KeyCollection
not a static copy; insteadDictionary<TKey, TValue>.KeyCollection
refers to the keys in the originalDictionary<TKey, TValue>
. Therefore, changes toDictionary<TKey, TValue>
continue to be reflected inDictionary<TKey, TValue>.KeyCollection
.
So it should be unsafe to change the dictionary while listing the keys of the dictionary.
You can fix this with one change. Change this line:
foreach (string dir in sourceDirInclusion.Keys)
For this:
foreach (string dir in sourceDirInclusion.Keys.ToList())
The extension method ToList()
will create an explicit copy of the list of keys, making it safe to modify the dictionary; the "base collection" will be the copy, not the original.
source to share
If he throws
InvalidOperationException: Message = "The collection was changed, the enumeration operation may not be performed
To avoid adding candidates for deletion to the external list. Then flip it over and remove it from the target container (dictionary).
List<string> list = new List<string>(sourceDirInclusion.Keys.Count);
foreach (string dir in sourceDirInclusion.Keys)
{
if (sourceDirInclusion[dir] == null)
list.Add(dir);
}
foreach (string dir in list)
{
sourceDirInclusion.Remove(dir);
}
source to share
check this out: What's the best way to modify the list in a foreach loop?
In short:
The collection used in foreach is immutable. This is very much by design.
As MSDN says:
The foreach statement is used to iterate over a collection to get the information it needs, but cannot be used to add or remove items from the original collection to avoid unpredictable side effects. If you need to add or remove items from the original collection, use a for loop.
UPDATE: You can use a for loop instead:
for (int index = 0; index < dictionary.Count; index++) {
var item = dictionary.ElementAt(index);
var itemKey = item.Key;
var itemValue = item.Value;
}
source to share
This works because you are looking at sourceDirInclusion.Keys.
However, to be sure of future versions of FrameWork, I recommend that you use sourceDirInclusion.Keys.ToArray () in the foreach statement so you create a copy of the keys you are executing.
However, this won't work:
foreach(KeyValuePair<string, object> item in sourceDirInclusion)
{
if (item.Value == null)
sourceDirInclusion.Remove(item.Key);
}
Typically, you cannot modify a collection while traversing it, but often you can create a new collection using .ToArray () or .ToList () and navigate around changing the original collection.
Good luck with your search.
source to share