Collections & LINQ · Lesson 23

Removing selected list items safely

Use a predicate-based bulk removal instead of mutating a foreach traversal.

How it works

A List is mutable, but not every traversal permits structural changes while it is in progress. Removing elements inside a foreach loop can invalidate its enumerator. RemoveAll expresses a common safe alternative: supply a predicate describing the items to remove, let the list perform the operation, and receive the number of removed items as its result.

A removal predicate should be easy to explain and free from unrelated side effects. Treat it as a decision about one element. The method mutates the existing list, so every other reference to that list observes the same change. If callers need an independent filtered result while retaining the original sequence, a Where query followed by ToList expresses a different ownership and mutation policy.

Walk through the example. The sample list contains two expired entries, represented by negative quantities. RemoveAll removes exactly those entries and returns two. The remaining zero and positive values keep their relative order. A separate filtered copy is then created from the remaining list. Clearing that copy proves it has its own list container and does not clear the original list.

Watch the boundary. A predicate that changes the collection it is evaluating makes the operation much harder to reason about. Do not append, remove, or reorder the same list inside that predicate. Also distinguish removing an element from deleting any external resource it represents: removing a record from a list does not automatically close a file, cancel work, or update another system.

Practice independently. Create a task list with completed and active records, then implement two operations: remove completed tasks in place, and build an independent active-task list. Verify both result counts and the original order of survivors. Test no matches, all matches, and an empty list. Explain what another variable referring to the original list will observe after each operation.

A working example

using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

var quantities = new List<int> { 3, -1, 0, 7, -2 };
int removed = quantities.RemoveAll(value => value < 0);
Console.WriteLine($"removed:{removed}");
Console.WriteLine(string.Join(",", quantities));
var copy = quantities.Where(value => value > 0).ToList();
copy.Clear();
Console.WriteLine(quantities.Count);

Expected output

removed:2
3,0,7
3
Try this
Choose between mutating the existing list and creating an independent filtered list.