In this article, we are going to look at different ways to remove elements from a generic list while iterating through it. 

To download the source code for this article, you can visit our GitHub repository.

If we try simply to do a test and call Remove when we want to discard an element, the solution would throw an exception:

private static readonly List<int> _numberList = Enumerable.Range(1, 20).ToList();

public static List<int> NaiveIterateRemove(List<int> sourceList)
{
    foreach (var el in sourceList)
    {
        if (el % 2 == 0)
        {
            sourceList.Remove(el);
        }
    }

    return sourceList;
}

Here, we define a simple List of numbers in a range and attempt to remove the even elements as we loop through it. However, this fails with InvalidOperationException.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

We can get around this error in a number of ways depending on what feels clean to us (stylistically). We will also take a look at the comparative performance of the different methods.

Using For/Foreach to Remove Elements From a Generic List

The simplest solution is to do the same thing as in the naive example, only go backward with a for loop. We can then safely remove elements because they are being removed from the “end” of the List and do not affect the iteration:

public static List<int> ReverseIterate(List<int> sourceList)
{
    for (int i = sourceList.Count - 1; i >= 0; i--)
    {
        if (sourceList[i] % 2 == 0)
        {
            sourceList.RemoveAt(i);
        }
    }

    return sourceList;
}

Here we iterate backward over the List and call RemoveAt with the index of the element that we wish to remove. This works well and is easy to understand.

However, by simply making a copy of the List using the ToList() method, we can rather use foreach method and iterate forward:

public static List<int> SimpleIterateRemoveWithToList(List<int> sourceList)
{
    foreach (var el in sourceList.ToList())
    {
        if (el % 2 == 0)
        {
            sourceList.Remove(el);
        }
    }

    return sourceList;
}

Here we again iterate forward through the list using a foreach loop. This is effectively identical code to the initial example with the exception that we are now iterating through a copy of the List while removing from the original List. If we saw this in the production code, we might not initially understand why we call ToList() on an object that was already a List.

Using RemoveAll to Remove Elements From a Generic List

We can also use the built-in RemoveAll method with a predicate instead of using a loop. This is a simple, fast, and readable solution that modifies the original list:

sourceList.RemoveAll(el => el % 2 == 0);

The only problem with this is that we often need to perform some action on the elements during the iteration. Fortunately, we can still use RemoveAll in this case by using a lambda function for the predicate:

public static List<int> OneLineRemoveAllWithSideEffect(List<int> sourceList)
{
    sourceList.RemoveAll(item =>
    {
        PerformOperation(item);
        return item % 2 == 0;
    });
    ;
    return sourceList;
}

Here we again use the RemoveAll method, but we also use a lambda function as the argument rather than a simple predicate. The return value of this lambda function determines if the element is to be removed. We can use this form to operate on each element within the body of the lambda.

This provides us with a more functional style but is actually no simpler or more compact than a simple foreach loop.

Testing Performance

We will have a quick look at the performance of these different methods to help us decide what might be best to use:

|                           Method |      Mean |     Error |    StdDev |    Allocated |
|--------------------------------- |----------:|----------:|----------:|-------------:|
|                RunReverseIterate |  6.640 ms | 0.0504 ms | 0.0447 ms |          4 B |
| RunSimpleIterateRemoveWithToList | 21.411 ms | 0.4044 ms | 0.4814 ms | 40,010,112 B |
|              RunOneLineRemoveAll | 13.119 ms | 0.0664 ms | 0.0554 ms |          8 B |

We get these results by running BenchmarkDotNet. For this benchmark, we are using a 200 000 integer array and performing 100 iterations of each method.

As a result, we can see that running a raw reverse for loop is easily the most efficient in time and memory. Note that “Allocated” memory measures are used by the method under test. Whereas the actual data used exists at a class level.

As we expect, using ToList is very memory intensive due to making copies of the data per iteration. 

Conclusion

Realistically, in many applications making copies of simple Lists is not really any cause for concern. But that’s not the case if we are dealing with massive arrays or large numbers of operations.

Ultimately, the final choice we make is really a combination of stylistic preferences and performance requirements. If we prefer a functional style of coding then using RemoveAll may appear neater. If we prefer code that is easy to read, using ToList with foreach is appealing. And if we are writing a performance-critical application then using the reverse for loop is ideal (although it may be beneficial to use a different data structure in this case).

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!