CodeGym /课程 /C# SELF /LINQ to XML: XDocument

LINQ to XML: XDocument, XElement

C# SELF
第 48 级 , 课程 3
可用

1. 介绍

LINQ 的出现彻底改变了 .NET 里处理 XML 的方式。以前修改 XML 常常要写一堆笨重的结构,像 NodeListXmlElement 那样,现在我们可以用 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 很方便在内存里直接构建,靠 XElementXDocument 的构造器。下面示例展示如何往文档里加一个新的人(记住,这是我们“成长中的通讯录”应用的一部分):

// 创建新的 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. 有用的方法和技巧:XElementXDocument

导航

  • .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 方案比一比

方式 何时使用 优点 缺点
XmlDocument
遗留代码、老项目 与 XPath 集成 代码多,API 复杂
XmlSerializer
用于对象序列化 方便地序列化 C# ↔ XML 灵活性低
LINQ to 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 结构。它不返回任何东西,删除后不能“撤销”。新手有时期待它会返回被删的元素或以某种方式提示改动——事实并非如此。

2
任务
C# SELF, 第 48 级, 课程 3
已锁定
从XML文档中提取数据
从XML文档中提取数据
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION