Supprimer des éléments dans une liste C# paraît simple, mais en pratique la bonne méthode dépend de ce que tu sais déjà sur l’élément à retirer: sa valeur, son index ou une condition. Le sujet derrière c# list remove se joue surtout entre Remove, RemoveAt, RemoveRange, RemoveAll et Clear, avec des pièges très concrets sur l’égalité, les performances et l’itération. Je vais te montrer comment choisir la bonne approche, quand le code devient fragile, et comment éviter les erreurs qui reviennent sans cesse dans les projets C#.
Les points essentiels à garder avant de modifier une List
-
Removesupprime la première occurrence d’une valeur et renvoie un booléen. -
RemoveAttravaille par index et décale tous les éléments qui suivent. -
RemoveAllest le bon choix quand tu veux filtrer selon une règle métier. -
foreachet suppression ne font pas bon ménage si tu modifies la collection pendant l’itération. -
Clearvide la liste, mais ne réduit pas forcément sa capacité mémoire. - Pour les objets personnalisés, l’égalité compte autant que la méthode choisie.
Ce que signifie vraiment supprimer dans une List
Dans une List, supprimer un élément ne veut pas seulement dire “effacer une case”. La liste se compacte immédiatement: les éléments situés après la suppression sont renumérotés et glissent vers la gauche. C’est pour ça que les index changent tout de suite, et c’est aussi la raison pour laquelle plusieurs opérations de suppression peuvent coûter plus cher qu’on ne l’imagine.
La documentation Microsoft rappelle aussi un point souvent négligé: Remove s’appuie sur l’égalité par défaut de T. Autrement dit, si tu manipules une classe personnalisée, deux instances qui “se ressemblent” ne seront pas forcément considérées comme identiques si Equals et GetHashCode ne sont pas cohérents. En clair, la suppression dépend autant de la structure de tes données que de la méthode appelée.
Une fois cette mécanique en tête, le premier cas d’usage devient très concret: retirer une valeur précise sans toucher au reste.
Retirer la première occurrence d’une valeur
Remove est la méthode la plus lisible quand tu connais la valeur exacte à supprimer. Elle enlève la première occurrence trouvée, puis renvoie true si la suppression a réussi, ou false si la valeur n’était pas présente. C’est pratique pour du code simple, des doublons tolérés, ou des listes de chaînes et d’objets dont l’égalité est bien définie.
var fruits = new List { "Pomme", "Banane", "Poire", "Banane" };
if (fruits.Remove("Banane"))
{
Console.WriteLine("Première occurrence supprimée.");
}
Console.WriteLine(string.Join(", ", fruits));
Ce petit détail change beaucoup de choses: si ta liste contient plusieurs fois la même valeur, une seule disparaît. Je garde donc Remove pour les suppressions ponctuelles, pas pour vider une catégorie entière. Quand l’élément est connu par sa position plutôt que par sa valeur, la logique devient différente.
Supprimer par position ou par bloc
Quand tu as un index fiable, RemoveAt est plus direct. L’index est en base zéro, donc le premier élément est à 0, le deuxième à 1, et ainsi de suite. La méthode retire un seul élément, puis décale le reste. Microsoft indique aussi que l’opération est en O(n), parce que les éléments après la position supprimée doivent être déplacés.
RemoveRange sert au même genre de travail, mais sur un bloc contigu. C’est utile quand tu veux enlever plusieurs éléments d’un coup à partir d’un point de départ précis. Les deux méthodes exigent des bornes valides: un index invalide ou une plage impossible déclenche une exception, donc je les utilise surtout quand l’état de la liste est bien maîtrisé.
var items = new List { "A", "B", "C", "D", "E" };
items.RemoveAt(2); // retire "C"
var more = new List { "A", "B", "C", "D", "E" };
more.RemoveRange(1, 2); // retire "B" et "C"
Je préfère cette famille de méthodes quand le besoin est “structurel”: je sais exactement où retirer, et je veux le faire sans introduire une logique de filtrage plus compliquée. Dès que la règle dépend d’un test métier, RemoveAll devient généralement plus propre.

Supprimer plusieurs éléments selon une condition
RemoveAll est la méthode que j’utilise le plus souvent dès qu’il faut filtrer une liste. Elle prend un predicat, c’est-à-dire une fonction qui retourne true ou false selon qu’un élément doit être retiré ou non. La méthode parcourt la liste, supprime tous les éléments qui matchent et renvoie le nombre total d’éléments retirés.
var users = new List
{
new User("Alice", true),
new User("Nadia", false),
new User("Lucas", true),
new User("Mina", false)
};
int removed = users.RemoveAll(u => !u.IsActive);
Console.WriteLine($"{removed} compte(s) supprimé(s).");
Ce pattern est intéressant parce qu’il traduit directement une règle métier: “garder les utilisateurs actifs”, “retirer les fichiers expirés”, “supprimer les produits hors stock”. La documentation Microsoft montre que RemoveAll traverse la liste du début à la fin et applique la condition à chaque élément, ce qui en fait une bonne réponse pour la plupart des filtres simples. Le vrai piège, lui, n’est pas la méthode: c’est la manière dont on la combine avec l’itération.
Les erreurs qui cassent souvent la suppression
La plus classique reste la modification d’une collection pendant un foreach. Microsoft le documente clairement: si tu ajoutes ou supprimes des éléments pendant l’itération, tu risques une InvalidOperationException avec un message du type “Collection was modified; enumeration operation may not execute.” En pratique, ça casse le programme au moment où tu t’y attends le moins.
// Mauvaise idée : la collection est modifiée pendant foreach.
foreach (var item in items)
{
if (ShouldRemove(item))
{
items.Remove(item);
}
}
Quand je dois absolument supprimer en parcourant la liste manuellement, je pars à rebours avec un for. L’astuce est simple: comme les éléments se décalent après une suppression, parcourir de la fin vers le début évite de sauter un élément ou de dérégler l’index courant.
for (int i = items.Count - 1; i >= 0; i--)
{
if (ShouldRemove(items[i]))
{
items.RemoveAt(i);
}
}
Dans beaucoup de cas, je ne m’embête même pas avec ça: si la règle tient en une expression, RemoveAll est plus lisible et plus sûr. Une fois les pièges identifiés, le plus utile reste de comparer les méthodes entre elles pour choisir vite et juste.
Quelle méthode j’utilise selon le besoin
| Besoin | Méthode | Ce que ça fait | Quand je la choisis |
|---|---|---|---|
| Retirer une valeur précise | Remove(item) |
Supprime la première occurrence et renvoie un booléen | Cas simple, doublons tolérés |
| Retirer un élément connu par son index | RemoveAt(index) |
Supprime l’élément à la position indiquée | Index stable, logique directe |
| Retirer un bloc contigu | RemoveRange(index, count) |
Supprime plusieurs éléments d’un seul coup | Plage connue à l’avance |
| Retirer selon une règle | RemoveAll(predicate) |
Supprime tous les éléments qui respectent la condition | Filtrage métier, nettoyage de liste |
| Vider complètement la liste | Clear() |
Retire tous les éléments | Réinitialisation totale |
Mon réflexe est simple: si la règle tient en une phrase, je pars sur RemoveAll; si je connais une valeur exacte, j’utilise Remove; si j’ai un index sûr, je prends RemoveAt. Pour un vide complet, Clear() est le plus net, et il laisse souvent la capacité mémoire intacte, ce qui peut être utile si la liste doit repartir immédiatement. Le vrai critère de choix n’est donc pas la syntaxe, mais le type d’information que tu possèdes au moment de supprimer.
Et quand les suppressions deviennent fréquentes, je regarde aussi le volume. Par simple combinaison des coûts en O(n) annoncés par Microsoft, enchaîner beaucoup de Remove ou de RemoveAt peut finir par coûter cher, parfois jusqu’à un comportement proche de O(n²) si la liste est très sollicitée. C’est une déduction pratique, pas une règle abstraite, mais elle explique pourquoi je préfère souvent filtrer une fois plutôt que supprimer élément par élément.
La règle pratique que j’applique quand la liste devient sensible
Quand je sens que la suppression devient un point chaud, je reviens à une règle simple: je supprime selon le besoin réel, pas selon l’habitude. Pour un retrait unique, Remove. Pour une position, RemoveAt. Pour un bloc, RemoveRange. Pour une logique de filtre, RemoveAll. Et si le flux métier me pousse à supprimer énormément d’éléments, je n’hésite pas à reconstruire une nouvelle liste filtrée plutôt que d’empiler des suppressions unitaires.
C’est plus lisible, plus testable, et souvent plus prévisible en performance. Dans un codebase C# un peu sérieux, cette discipline évite déjà une bonne partie des bugs silencieux autour des index, des doublons et des collections modifiées au mauvais moment.