C#如何解析XML?完整教程与代码实例

来源:Nginx教程作者:灯下变量头衔:程序员
导读:本期聚焦于灯下变量创作的《C#如何解析XML?完整教程与代码实例》,敬请观看详情。解析XML是C#开发中常见的需求,无论是读取配置文件、处理接口返回数据还是交换业务报文,都离不开对XML结构的遍历与取值。本文从实际场景出发,系统梳理C#中解析XML的几种主流方式,包括基于DOM的XmlDocument、基于流式读取的XmlReader以及支持LINQ查询的XDocument。文章会对比它们的内存占用、读取速度与易用性,并给出完整可运行的代码实例,覆盖节点遍历、属性读取、XPath过滤、命名空间处理等常见操作。同时还会说明如何避免XML外部实体注入等安全风险。读完本文,你可以根据文件大小和业务需求选择最合适的解析方案。

在C#中处理XML数据时,最常见的做法是使用XmlDocument、XDocument或XmlReader。它们分别对应不同的内存模型和读取策略:XmlDocument构建完整DOM树,适合需要随机访问节点和修改结构的场景;XDocument提供更现代的LINQ查询体验;XmlReader则以流式方式逐节点读取,适合超大文件。本文会从基础遍历、属性读取到XPath筛选、命名空间处理,逐步演示这些解析器的用法。

C#如何解析XML?完整教程与代码实例

一、XML解析方案对比与选型思路

在开始编码之前,先明确不同解析器的定位。XmlDocument基于W3C DOM标准,在.NET Framework早期就被引入,至今仍被大量项目使用。它一次性把整个XML文档读入内存并构建节点树,支持通过父子关系、XPath等方式随时访问任意节点,也能对树进行增删改操作。优点是API稳定、资料丰富,缺点是内存占用较大,当XML达到几十MB甚至更大时容易出现性能瓶颈。

XDocument与XmlDocument类似,同样构建完整的内存树,但它属于LINQ to XML体系,可以通过Descendants、Elements、Attributes等方法写出链式查询。对于熟悉LINQ的开发者来说,代码通常更短、可读性更强。而XmlReader则完全不同,它不构建文档树,而是像游标一样按顺序读取每个XML节点,内存占用非常低,代价是不能随机回退,编写逻辑也更繁琐。

解析器读取方式内存占用典型场景
XmlDocumentDOM树高小中型文件、需要随机访问与修改
XDocumentDOM树 + LINQ高中小型文件、复杂查询
XmlReader只进流低大型XML、顺序读取

综合来看,如果XML文件小于10MB,并且你需要频繁修改节点或按任意条件查询,直接使用XDocument或XmlDocument会更省事;如果只是从头到尾读一遍、提取少量字段,XmlReader的流式模型更合适。下文会依次给出三者的典型用法。

二、使用XmlDocument解析XML

XmlDocument是DOM解析的代表。假设有一段XML数据,根节点是<books>,下面包含多个<book>节点,每个<book>带有id属性,并包含<title>子节点。先看最简单的加载与遍历代码:

XmlDocument doc = new XmlDocument();
doc.LoadXml("<books><book id=\"1\"><title>C#入门</title></book></books>");
XmlNode root = doc.DocumentElement;
foreach (XmlNode node in root.ChildNodes)
{
    XmlElement book = (XmlElement)node;
    string id = book.GetAttribute("id");
    string title = book["title"].InnerText;
    Console.WriteLine($"{id}: {title}");
}

上面的代码先调用LoadXml解析字符串,也可以通过Load方法直接加载文件路径。DocumentElement返回根元素,ChildNodes列出所有直接子节点。由于节点类型不一定都是元素,实际项目中通常需要判断NodeType,这里为了演示做了直接转换。

如果需要按条件查询,可以使用SelectNodes和SelectSingleNode配合XPath表达式。下面代码获取所有标题,并查找id为2的书籍:

XmlNodeList titles = doc.SelectNodes("//book/title");
foreach (XmlNode title in titles)
{
    Console.WriteLine(title.InnerText);
}

XmlNode bookWithId = doc.SelectSingleNode("//book[@id='2']");
Console.WriteLine(bookWithId?.InnerText);

使用XPath可以减少手写递归遍历,但要注意XPath表达式是字符串,写错时编译器不会提示,运行时才会暴露。XmlDocument在读取包含命名空间的XML时,还需要创建XmlNamespaceManager并注册前缀,否则SelectNodes可能返回空结果。这是它相对繁琐的地方。

三、使用XDocument和LINQ to XML

XDocument属于LINQ to XML,语法上更贴近现代C#风格。同样的XML数据,用XDocument.Parse解析后,可以直接通过Descendants获取所有<book>元素,再用Element读取子节点。代码中甚至可以使用匿名类型把结果投影出来:

XDocument xdoc = XDocument.Parse("<books><book id=\"1\"><title>C#入门</title></book></books>");
var titles = xdoc.Descendants("book")
                 .Select(book => new
                 {
                     Id = (string)book.Attribute("id"),
                     Title = (string)book.Element("title")
                 });
foreach (var item in titles)
{
    Console.WriteLine($"{item.Id}: {item.Title}");
}

这段代码不需要像XmlDocument那样手动转换XmlNode为XmlElement,类型转换也更加直观:通过(string)显式转换可以安全地把属性值转为字符串,如果属性不存在则返回null。对于需要筛选、排序、分组等操作的项目,LINQ的延迟执行和链式调用非常方便。

XDocument同样支持Load加载文件、Save写回文件,也可以对树进行增删改。例如遍历文件中的节点:

XDocument xdoc = XDocument.Load("books.xml");
foreach (XElement book in xdoc.Root.Elements("book"))
{
    string id = (string)book.Attribute("id");
    string title = (string)book.Element("title");
    Console.WriteLine($"{id}: {title}");
}

从性能角度看,XDocument与XmlDocument没有本质差别,因为它们都构建完整的内存树。但前者的代码更简洁,适合团队已经熟悉LINQ的项目。如果XML结构非常简单,也可以考虑直接使用XElement,它比XDocument更轻量一些。

四、使用XmlReader流式读取大型XML

当XML文件达到上百MB时,DOM解析器的内存占用会快速上升,甚至触发OutOfMemoryException。这时应当切换到XmlReader。XmlReader通过Read方法逐个读取节点,每次只保留当前节点的信息,因此内存曲线非常平缓。下面的代码从一个假设的大型文件中按顺序提取<book>节点:

using (XmlReader reader = XmlReader.Create("large.xml"))
{
    while (reader.Read())
    {
        if (reader.NodeType == XmlNodeType.Element && reader.Name == "book")
        {
            string id = reader.GetAttribute("id");
            reader.ReadToDescendant("title");
            string title = reader.ReadElementContentAsString();
            Console.WriteLine($"{id}: {title}");
        }
    }
}

这段代码没有把整个文件加载到内存,而是每遇到一个<book>元素就读取属性,然后移动到<title>子节点并获取文本内容。ReadToDescendant会从当前位置开始查找指定名称的后代元素。如果XML层级较深或者结构复杂,这种手工状态管理会比较累,但这正是换取低内存占用的代价。

XmlReader也支持ReadToFollowing、ReadElementContentAsString等方法,配合XmlReaderSettings可以控制忽略注释、处理空白、指定编码等行为。在实际项目中,如果只是读取数据一次,并且不需要修改XML,流式读取通常是最稳妥的选择。

五、命名空间处理与安全注意事项

很多XML文档会声明默认命名空间,例如根节点写为<books xmlns="http://ipipp.com/ns">。如果不加处理,直接查询book或title通常得不到任何节点,因为元素名实际上是带命名空间的全名。XDocument处理命名空间比较直观,使用XNamespace对象拼接即可:

XNamespace ns = "http://ipipp.com/ns";
var titles = xdoc.Descendants(ns + "title");
foreach (var title in titles)
{
    Console.WriteLine((string)title);
}

在XmlDocument中,则需要XmlNamespaceManager注册前缀,然后在XPath中使用ns:前缀。忽略命名空间是XML解析中最常见的坑之一,排查时表现为代码没有报错但结果一直为空。

另一个容易被忽视的问题是XML外部实体注入。如果解析的XML来自不可信来源,直接使用默认的XmlDocument.Load可能解析外部实体,从而造成信息泄露或拒绝服务。建议设置XmlReaderSettings禁止DTD与外部解析器:

XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
settings.XmlResolver = null;
using (XmlReader reader = XmlReader.Create("input.xml", settings))
{
    XmlDocument doc = new XmlDocument();
    doc.Load(reader);
}

这样可以在加载阶段阻断大部分与DTD相关的攻击。处理配置文件时风险较小,但处理用户上传或第三方接口返回的XML时,应始终默认不信任外部实体。

总体来说,C#解析XML并不复杂,关键是根据文件大小、查询方式和安全要求选择合适的解析器。小文件优先考虑XDocument,需要老API兼容的项目可以继续使用XmlDocument,大文件则用XmlReader流式处理。掌握这三种方式后,绝大多数XML读写场景都能轻松应对。

C# XML解析XmlDocumentXPath修改时间:2026-09-19 06:15:19

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0919/59125.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。