1. 介绍
LINQ 的出现彻底改变了 .NET 里处理 XML 的方式。以前修改 XML 常常要写一堆笨重的结构,像 NodeList 和 XmlElement 那样,现在我们可以用 LINQ 风格把操作表达得很清爽。就像从颠簸的土路突然开上了高速。
LINQ to XML 解决了三件事:
- 简化了构建和读取 XML 的语法。
- 让在文档里导航看起来像在操作集合。
- 能把 XML 跟任何 LINQ 查询无缝结合。
主要类: XDocument, XElement, XAttribute
在跳进 LINQ to XML 的深水之前,先认识三块基石:
- XDocument — 整个文档的根,相当于书的封面,里面装着所有内容。
- XElement — 任意一个 XML 节点(元素)。就像仓库里的一个箱子,里面可以有别的箱子或货物(元素、属性和文本)。
- XAttribute — 元素的属性,例如 <User id="42" />。
可视化示意
+-----------------+
| XDocument |---------------------+
+-----------------+ |
| (Root XElement)
v |
+-------------+ +-----------------+
| XElement |<-------------| XElement (root) |
+-------------+ +-----------------+
| |
(XAttribute) (nested XElements)
2. 使用 LINQ to XML 读取 XML
假设在我们的教学应用里(比如一个通讯录)想从 XML 文件读取联系人。文件结构可以是这样:
<Contacts>
<Person id="1">
<Name>伊万 伊万诺夫</Name>
<Email>ivan@example.com</Email>
<Phones>
<Phone type="mobile">+12 999 123-45-67</Phone>
<Phone type="home">+12 812 123-45-67</Phone>
</Phones>
</Person>
<Person id="2">
<Name>玛丽亚 彼得罗娃</Name>
<Email>maria@example.com</Email>
<Phones>
<Phone type="mobile">+12 921 123-45-67</Phone>
</Phones>
</Person>
</Contacts>
把 XML 读进内存
先在项目里加上引用:
using System.Xml.Linq;
现在读取文件或字符串:
// 假设 xmlString 包含 XML 文档,或者可以使用 XDocument.Load("contacts.xml")
var doc = XDocument.Parse(xmlString);
// 获取 Person 列表
var persons = doc.Root.Elements("Person");
foreach (var person in persons)
{
string name = person.Element("Name")?.Value ?? "无名";
string email = person.Element("Email")?.Value ?? "没有 e-mail";
string id = person.Attribute("id")?.Value ?? "没有 id";
Console.WriteLine($"姓名: {name}, Email: {email}, id: {id}");
}
- doc.Root 返回根元素(Contacts)。
- Elements("Person") 返回所有的 <Person>。
- 然后通过 .Element("...") 或 .Attribute("...") 取所需的元素/属性。
- 操作符 ?. 可以防止在元素/属性缺失时抛异常。
类比
操作 XElement 就像拆套娃:拿出一层,里面还有别的,层层嵌套。但是现在你可以用一句轻巧的查询代替繁琐的遍历。
3. 用 LINQ 写 XML 查询
想拿到所有人的所有手机号码?没问题,LINQ 风格一下就能搞定:
var mobilePhones = doc
.Descendants("Phone") // 在任意层级查找所有 Phone
.Where(p => (string)p.Attribute("type") == "mobile")
.Select(p => p.Value);
foreach (string phone in mobilePhones)
{
Console.WriteLine($"手机: {phone}");
}
- Descendants("Phone") 会查找文档中所有的 <Phone>。
- Attribute("type") 用来取属性。
- 对属性做 (string) 强制转换会自动返回字符串或 null。
有趣的小技巧
可以跨多个层级查找而不必关心复杂的嵌套。用传统的 XmlDocument 想实现同样的效果通常会更麻烦。
4. 动态创建和修改 XML
XML 很方便在内存里直接构建,靠 XElement 和 XDocument 的构造器。下面示例展示如何往文档里加一个新的人(记住,这是我们“成长中的通讯录”应用的一部分):
// 创建新的 Person
var newPerson = new XElement("Person",
new XAttribute("id", "3"),
new XElement("Name", "谢尔盖 诺维科夫"),
new XElement("Email", "sergey@example.com"),
new XElement("Phones",
new XElement("Phone", new XAttribute("type", "work"), "+12 812 987-65-43")
)
);
// 添加到根节点
doc.Root.Add(newPerson);
// 保存回文件(或转换为字符串)
doc.Save("contacts.xml");
备注:
- 可以通过“套娃式”构造器来嵌套元素:第一个参数是标签,其余参数是内部内容(可能是元素、属性、文本)。
- 属性总是在直接内容之前写(比如 <Phone type="work">...)。
5. 和 C# 集合互动—LINQ 的魔法
经常需要把普通的对象集合(比如 List<Person>)序列化成 XML,或反过来。用 LINQ 基本上一行就能搞定:
var people = new List<Person>
{
new Person { Id = 1, Name = "伊万 伊万诺夫", Email = "ivan@example.com" },
// 等等
};
var doc = new XDocument(
new XElement("Contacts",
people.Select(p =>
new XElement("Person",
new XAttribute("id", p.Id),
new XElement("Name", p.Name),
new XElement("Email", p.Email)
)
)
)
);
为了完整,Person 类长这样:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
现在你有了一个简单、直观并且可控的方式,把 C# 集合转换成 XML!
6. 有用的方法和技巧:XElement 与 XDocument
导航
- .Elements("TagName") — 所有直接子元素(直接后代)。
- .Descendants("TagName") — 在当前元素内部任意位置的所有嵌套元素。
- .Parent — 父元素。
- .Ancestors() — 元素的所有祖先(查找路径时有用)。
- .FirstNode, .LastNode, .NextNode, .PreviousNode — 在相邻节点间导航。
带条件的查找
var personsWithEmail = doc.Root
.Elements("Person")
.Where(p => !string.IsNullOrEmpty((string)p.Element("Email")));
添加与删除
// 给已存在的用户加个电话
var maria = doc.Root.Elements("Person")
.FirstOrDefault(p => (string)p.Element("Name") == "玛丽亚 彼得罗娃");
maria?.Element("Phones")?.Add(
new XElement("Phone", new XAttribute("type", "work"), "+12 812 111-22-33")
);
// 按 id 删除用户
doc.Root.Elements("Person")
.Where(p => (string)p.Attribute("id") == "3")
.Remove();
转换回字符串/文件
string xmlText = doc.ToString(); // 带缩进
doc.Save("contacts.xml");
7. 有用的小细节
把 LINQ to XML 和其他 .NET 方案比一比
| 方式 | 何时使用 | 优点 | 缺点 |
|---|---|---|---|
|
遗留代码、老项目 | 与 XPath 集成 | 代码多,API 复杂 |
|
用于对象序列化 | 方便地序列化 C# ↔ XML | 灵活性低 |
|
其他大多数场景:解析、修改 | 线性、简洁、强大,LINQ 风格 | 占用内存稍多 |
在真实项目和面试中的应用
- 解析设置文件,当没有明确对应的 C# 类时。
- 数据导入/导出(比如用户数据的导出/导入)。
- 迁移、处理“脏”或复杂的 XML 结构。
- 快速转换(比如把一种 XML 格式转换成另一种)。
“我为啥要学这个?”——在面试里可能让你把一个复杂的 XML 解析或转换掉,掌握 LINQ to XML 会让你比用老的 API 更快更漂亮地完成任务。
8. 使用 LINQ to XML 时的常见错误
错误 №1:在没有检查的情况下访问可能不存在的元素。
如果元素可能缺失,用操作符 ?. 或先检查是否为 null。否则会抛出 NullReferenceException,运行时直接炸掉程序。
错误 №2:把属性和元素搞混了。
属性和元素是 XML 中不同的东西。数据有时会在属性里,有时在元素里。例如:
<User login="admin"><Status>active</Status></User>
要取属性 login 的值,用 .Attribute("login");要取标签 <Status> 的内容,用 .Element("Status")。
错误 №3:误解 .Remove() 的行为。
方法 .Remove() 会立即修改内存中的 XML 结构。它不返回任何东西,删除后不能“撤销”。新手有时期待它会返回被删的元素或以某种方式提示改动——事实并非如此。
GO TO FULL VERSION