Remove()按值删第一个匹配项,值不存在则无操作;RemoveAt()按索引删除,越界抛ArgumentOutOfRangeException;误用Remove()无法批量删同值元素。

用 Remove() 和 RemoveAt() 删除单个元素时要注意什么
这两个方法最常用,但行为完全不同:Remove() 按值匹配第一个相等元素并删除,RemoveAt() 按索引删除。如果值不存在,Remove() 什么也不做且不报错;而 RemoveAt() 索引越界会直接抛 ArgumentOutOfRangeException。
常见错误是误用 Remove() 试图删多个同值元素——它只删第一个。比如 list.Remove(5) 遇到 [5, 5, 1] 后变成 [5, 1],不是预期的 [1]。
实操建议:
- 确认要删的是“某个值”还是“第几个位置”,再选方法
- 用
RemoveAt()前务必检查index >= 0 && index - 若需删所有匹配项,别用循环调
Remove()(可能跳过元素),改用RemoveAll()
批量删除必须用 RemoveAll(),别手写 for 循环
在遍历 List 时用 for 或 foreach 调 Remove(),极易因索引偏移漏删或越界。正确做法是把筛选逻辑交给 RemoveAll(),它内部一次性处理,安全高效。
示例:删掉所有偶数
list.RemoveAll(x => x % 2 == 0);
注意点:
- 谓词函数返回
true的元素会被删除 - 返回值是实际删除的元素个数,可用于校验
- 不能在谓词里修改
list本身(比如再调RemoveAll),会抛InvalidOperationException - 对引用类型,判断依据是对象引用或重载的
Equals(),不是简单地址比较
按条件删又想保留原顺序?FindAll().ForEach() 不行,得用 Where().ToList()
有人想“找出要删的,再逐个删”,于是写 list.FindAll(x => x > 10).ForEach(list.Remove)——这会出错,因为 Remove() 修改了原集合,导致 ForEach 迭代器失效。
更糟的是,即使不报错,结果也不可靠:重复值、顺序错乱、部分漏删。
正确思路是“构造新集合”而非“边查边删”:
RemoveAll()
-
RemoveAll()原地操作,内存友好,适合大列表 -
Where().ToList()创建新列表,原list引用改变,适合需要保留旧引用场景(如被其他变量持有) - LINQ 方式可链式组合(如加
OrderBy()),RemoveAll()只能单条件
删除后立即访问 Count 或索引要小心线程安全
List 本身不是线程安全的。如果多个线程同时调 RemoveAll() 或混合读写,Count 可能不准,甚至抛 InvalidOperationException(“集合已修改”)。
没有锁的情况下,以下代码危险:
if (list.Count > 0) { var first = list[0]; }
因为 Count 检查和 [0] 访问之间可能已被其他线程删空。
解决方式:
- 单线程场景无需担心
- 多线程下要么加
lock,要么改用ConcurrentBag/ConcurrentQueue等线程安全集合(但它们不支持随机索引) - 若只是读多写少,可用
ToArray()快照一次再操作:var snapshot = list.ToArray();
真正容易被忽略的是:删除操作看似简单,但一旦混入循环、多线程或引用共享,行为就依赖执行时机和集合状态。动手前先明确——你要删的是“值”还是“位置”,是否允许多删,有没有并发,以及谁还在用这个 list 引用。










