必须先调用 tolist() 再 remove(),否则因延迟执行导致枚举器失效而抛出 invalidoperationexception;remove() 删除节点,removeall() 清空子节点;删除后需显式 save() 才持久化。

用 Remove() 前必须先调用 ToList()
直接对 XElement 集合调用 Remove() 会抛出 InvalidOperationException: Collection was modified。因为 linq 查询是延迟执行的,遍历时修改源集合会触发枚举器失效。
正确做法是先把要删的节点转成内存列表再操作:
var doc = XDocument.Load("data.xml"); var targets = doc.Root .Elements("item") .Where(x => (String)x.Attribute("status") == "inactive") .ToList(); // ← 关键!必须这一步 targets.ForEach(x => x.Remove());
- 不加
ToList(),Remove()在第一次调用后就让后续枚举失败 -
ToArray()也行,但ToList()更常见、语义更清晰 - 如果节点层级深(比如嵌套在
section/item),确保Elements()路径准确,别漏掉父级
Remove() 和 RemoveAll() 别混用
Remove() 是实例方法,作用于单个 XNode;RemoveAll() 是 XElement 的方法,清空它的所有子节点(不删自己)。两者完全不是一回事。
常见误写:doc.Root.Elements("item").Where(...).RemoveAll() —— 这会报错,因为 IEnumerable<xelement></xelement> 没有 RemoveAll() 方法。
- 删节点:用
.Remove()(每个节点单独调) - 清内容:用
someElement.RemoveAll()(只影响该元素内部) - 想删整个元素及其子树?只能用
.Remove()
条件判断时注意 NULL 和类型转换安全
XML 属性或元素值可能为 null,直接强转 (string)x.Attribute("id") 虽然不会崩(LINQ to XML 对 null 属性返回空字符串),但用 x.Element("price")?.Value 或 (decimal?)x.Element("price") 更稳妥。
尤其当条件涉及数值比较或日期时,隐式转换容易出错:
// 危险:若 price 为空或非数字,会得 0 或抛 FormatException var expensive = doc.Root.Elements("item") .Where(x => (decimal)x.Element("price") > 100) .ToList(); // 推荐:显式可空转换 + 判空 var expensive = doc.Root.Elements("item") .Where(x => (decimal?)x.Element("price") > 100) .ToList();
-
(T?)转换对 null 安全,结果为null而非默认值 - 字符串比较记得用
StringComparer.OrdinalIgnoreCase处理大小写 - 避免用
.Value直接取值,优先用强制转换(如(string)x.Attribute("name"))
删除后记得调用 Save() 才真正落盘
所有 Remove() 操作只在内存中修改 XDocument 树,不自动写回文件。很多人删完以为完事了,结果下次读还是老数据。
- 改完必须显式
doc.Save("data.xml") - 如果原文件被其他进程占用,
Save()会抛IOException,建议加简单重试或提示 - 想保留原文件备份?先
File.copy("data.xml", "data.xml.bak", true)再 Save
真正麻烦的往往不是怎么删,而是删完没存、存了没权限、或者条件写成了 == null 而不是 == "null" —— 这些地方一卡就是半小时。