InvalidOperationException when foreach a HashSet

InvalidOperationException: Collection was modified; enumeration operation may not execute.

I got a Collection error when foreach a hashset…

I was executing it in a update function, which is a ordinary Unity function. The hashset monofuncCBSet will be modified somewhere,but I dont think it is a multithread. I am cofused about the reason…

you cannot modify a collection’s content while it is being enumerated. That includes adding or removing items from the collection (e.g by deleting/destroying items).

1 Like
2 Likes

I was modifying this hashset somewhere else but not in this Update function. In consideration of no Multi-threading involved, I dont think I have changed the amount of the hashset.Could it be said that the MonoUpdate function was the real reason that have changed the content itself…

I tried to use list ,it worked, but for efficiency,it is not the best choice.Cause the hashset would be inserted or removed frequently. Is there some way I can ergodic the collection with high efficiency?

mono.MonoUpdate() is probably the thing that is changing monoFuncCBSet;

Use two different buffers to insert / remove upon insert / removal + monoFuncCBSet.

E.g.

  1. Upon insert → add to the insert buffer (different hashset) and remove from the removal buffer;

  2. Upon removal → add to the removal buffer (different hashset) and remove from the insertion buffer;

  3. Upon update:
    3.1. Foreach on the insert buffer → add items to the monoFuncCBSet;
    3.2. Foreach on the removal buffer → remove items from the monoFuncCBSet;
    3.3. Clear buffers above;
    3.4. Perform foreach on monoFuncCBSet.

This should allow you to modify monoFuncCBSet in a safe way, as well as modify update manager entries more than one time per frame safely.

Alternatively, you can use lists for the buffers, but I’ve found that its harder to remove from it / more ops than hashset.

I’m assuming you’re writing your own update manager, so this is what I’ve done for my own manager.

1 Like
if(m_removed == null) m_removed = new HashSet<...>(); else m_removed.Clear();
if(m_added == null) m_added = new HashSet<...>(); else m_added.Clear();

foreach(var item in hSet) {
  if(someRemovalTrigger) m_removed.Add(item);
  if(someAdditionTrigger) m_added.Add(item);
}

m_mySet.ExceptWith(m_removed);
m_mySet.UnionWith(m_added);
1 Like

I also propose using HashSetDelta, if you’re working with HashSets a lot

  public class HashSetDelta<T> {

    public HashSet<T> Removed;
    public HashSet<T> Added;

    public HashSetDelta() {
      Removed = new HashSet<T>();
      Added = new HashSet<T>();
    }

    public HashSetDelta(HashSet<T> removed, HashSet<T> added) {
      Removed = removed;
      Added = added;
    }

    public void ApplyTo(HashSet<T> hSet) {
      if(hSet == null) hSet = new HashSet<T>();
      if(Removed != null) hSet.ExceptWith(Removed);
      if(Added != null) hSet.UnionWith(Added);
    }

    public void AppendTo(HashSetDelta<T> delta) {
      if(delta != null) {
        if(Removed != null) delta.Removed.UnionWith(Removed);
        if(Added != null) delta.Added.UnionWith(Added);
      }
    }

    public void Clear() {
      Removed.Clear();
      Added.Clear();
    }

    static public HashSetDelta<T> Analyze(HashSet<T> previous, HashSet<T> current) {
      HashSet<T> added = null;
      if(current != null) {
        added = new HashSet<T>(current);
        if(previous != null) added.ExceptWith(previous);
      }

      HashSet<T> removed = null;
      if(previous != null) {
        removed = new HashSet<T>(previous);
        if(current != null) removed.ExceptWith(current);
      }

      return new HashSetDelta<T>(removed, added);
    }

  }
1 Like

Ye…,I am trying to use a single Update function instead…:slight_smile: