34

I have a hashset in C# that I'm removing from if a condition is met while iterating though the hashset and cannot do this using a foreach loop as below.

foreach (String hashVal in hashset) 
{
     if (hashVal == "somestring") 
     {
            hash.Remove("somestring");
     }
}

So, how can I remove elements while iterating?

Sam
  • 7,252
  • 16
  • 46
  • 65
aHunter
  • 3,490
  • 11
  • 39
  • 46

6 Answers6

59

Use the RemoveWhere method of HashSet instead:

hashset.RemoveWhere(s => s == "somestring");

You specify a condition/predicate as the parameter to the method. Any item in the hashset that matches the predicate will be removed.

This avoids the problem of modifying the hashset whilst it is being iterated over.


In response to your comment:

's' represents the current item being evaluated from within the hashset.

The above code is equivalent to:

hashset.RemoveWhere(delegate(string s) {return s == "somestring";});

or:

hashset.RemoveWhere(ShouldRemove);

public bool ShouldRemove(string s)
{
    return s == "somestring";
}

EDIT: Something has just occurred to me: since HashSet is a set that contains no duplicate values, just calling hashset.Remove("somestring") will suffice. There is no need to do it in a loop as there will never be more than a single match.

adrianbanks
  • 81,306
  • 22
  • 176
  • 206
  • Thanks what would the s represent? – aHunter Sep 26 '09 at 21:48
  • 's' represents the current item from within the hashset being evaluated. See the updated answer. – adrianbanks Sep 26 '09 at 21:53
  • This won't cause anything like a `ConcurrentModificationException` in Java? I wonder how C# managed that. – PatPeter Sep 29 '19 at 23:40
  • I'm locked into an upvote, but I should have downvoted this answer because it caused `System.InvalidOperationException: Collection was modified; enumeration operation may not execute.` when using `RemoveWhere` in a `foreach` loop. – PatPeter Oct 02 '19 at 01:00
  • @PatPeter The answer is correct, you're just using it wrong. You can't change a collection while iterating over it; the answer didn't suggest that you should. It said to use `RemoveWhere` _without_ the `foreach` loop. – Clonkex Nov 07 '21 at 01:10
10

You can't remove items from a collection while looping over it with an enumerator. Two approaches to solve this are:

  • Loop backwards over the collection using a regular indexed for-loop (which I believe is not an option in the case of a HashSet)
  • Loop over the collection, add items to be removed to another collection, then loop over the "to-be-deleted"-collection and remove the items:

Example of the second approach:

HashSet<string> hashSet = new HashSet<string>();
hashSet.Add("one");
hashSet.Add("two");

List<string> itemsToRemove = new List<string>();
foreach (var item in hashSet)
{
    if (item == "one")
    {
        itemsToRemove.Add(item);
    }
}

foreach (var item in itemsToRemove)
{
    hashSet.Remove(item);
}
Fredrik Mörk
  • 155,851
  • 29
  • 291
  • 343
  • The program is already quite memory intensive so I would rather not use another list. Thanks – aHunter Sep 26 '09 at 21:49
  • 1
    I would avoid using two foreach loop - one foreach loop is enough, see my answer – Oleg Vazhnev Jul 20 '11 at 22:52
  • "You can't remove items from a collection while looping over it with an enumerator." This statement is now false, at least as far as Dictionaries are concerned in Core 3.0+. Though I don't know why this hasn't been implemented for HashSets. – arkon Nov 09 '22 at 09:46
9

I would avoid using two foreach loop - one foreach loop is enough:

HashSet<string> anotherHashSet = new HashSet<string>();
foreach (var item in hashSet)
{
    if (!shouldBeRemoved)
    {
        anotherSet.Add(item);
    }
}
hashSet = anotherHashSet;
Sam
  • 7,252
  • 16
  • 46
  • 65
Oleg Vazhnev
  • 23,239
  • 54
  • 171
  • 305
0

For people who are looking for a way to process elements in a HashSet while removing them, I did it the following way

var set = new HashSet<int> {1, 2, 3};

while (set.Count > 0)
{
  var element = set.FirstOrDefault();
  Process(element);
  set.Remove(element);
}
Muhammad Charaf
  • 343
  • 3
  • 16
  • This works only if you want to remove all of them, but if you need to remove only certain elements that match a condition (as OP states) then this will become an infinite loop! If what you want is to process them all and remove them after processing, why not just do a `foreach` loop to process and after it finishes clean the set? – c-chavez Jan 11 '22 at 10:45
0

there is a much simpler solution here.

var mySet = new HashSet<string>();
foreach(var val in mySet.ToArray() {
   Console.WriteLine(val);
   mySet.Remove(val);
}

.ToArray() already creates a copy for you. you can loop to your hearts content.

kewur
  • 421
  • 5
  • 15
-1

Usually when I want to iterate over something and remove values I use:

 For (index = last to first)
      If(ShouldRemove(index)) Then
           Remove(index)
Nescio
  • 27,645
  • 10
  • 53
  • 72
  • Thanks I know that I can use a for loop put you can not access a HashSet using a index position in this way. If I was using C++ then I would simply use pointers put I can not do this in C#. – aHunter Sep 26 '09 at 21:44
  • You also may consider using a different data structure if possible. – Nescio Sep 26 '09 at 21:47
  • Since .Net 3.5 you can use [ElementAt](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.elementat?view=netframework-3.5), and in a Hashset, order doesn't matter. – c-chavez Jan 11 '22 at 10:53