Unity/C# 常用XML读写方式详解(LINQ to XML、XmlReader/Writer)_c# xmlreader
此文章将会介绍C#中进行XML读写的方式:LINQ to XML随机读写、XmlReader/Writer流式读写
用于初学者参考或作为工具书查阅。
- LINQ to XML
-
- 基本概念和类
-
- XElement常用方法
- 创建XML
- 查询:LINQ 查询语法(Query Syntax)
-
- 排序、中间值
- 编辑XML
- XMLReader/XMLWriter
-
- XML结构
-
- 常见的节点类型与示例:
- 使用场景和注意事项:
- MoveTo...()方法
-
- 详细说明:
- ReadTo...()方法
-
- 详细说明:
- 总结对比:
- 结束
LINQ to XML
首先介绍LINQ to XML,这是更现代、更直观的XML操作方式。它使得你可以使用 LINQ 查询语法对 XML 文档进行操作,同时也能执行插入、删除、修改等操作。
与XMLDocument相比,LINQ to XML效率更高、内存占用更低。
基本概念和类
如果你对XML结构略有疑问,在
流式处理XML章节有详细的XML结构介绍以及解析。
XDocument、XElement、XAttribute是最常用的元素,我使用🌟标记。
名称、属性和子元素XElement 对象可以包含多个 XAttribute 对象XElement 可以有一个文本节点,包含该元素的文本值))。通常用于定义文档的类型、结构和规则要获取某个元素节点的内容,可读取其Value属性,或者将其本身强制转换为预期的基础类型。
要设置某个元素节点的内容,可设置其Value属性,或者使用SetValue(Object o)方法
ℹ️注意:
这些元素都实现了向基础类型的强制转换运算符,即转换是安全且有意义的,例如:XElement root = XElement.Parse(\"abc def ghi\");//将 XElement 强制转换为 string,其值将自动为:root.valueConsole.WriteLine(\"root={0}\", (string)root);这将输出root的内容:
abc def ghi
XElement常用方法
XElement 是 XDocument 的基础构建块,是整个 XML 操作体系的核心,它包含常用的方法:
XElement方法重载:可以添加多个元素、文本、注释或其他节点。常用的重载包括:
1.
Add(params object[] content):接受多个对象,支持添加 XElement、XText、XComment 等。2.
Add(XElement element):添加单个元素。重载:可以移除单个节点或一组节点。
重载:可以指定过滤条件来获取符合条件的后代元素。例如:
1.
Descendants(XName name):返回符合特定名称的后代元素。2.
Descendants(XName name, XNamespace ns):返回符合特定名称和命名空间的后代元素。重载:可以指定过滤条件来获取符合条件的子元素。例如:
1.
Elements(XName name):返回符合特定名称的子元素。2.
Elements(XName name, XNamespace ns):返回符合特定名称和命名空间的子元素。重载:可以通过属性的名称来访问或设置属性值。
1.
Attribute(XName name):获取特定名称的属性。2.
Attribute(XName name, XNamespace ns):获取带命名空间的特定名称的属性。true 如果元素包含子元素,否则返回 false。重载:可以设置元素的文本内容或从子元素中获取文本。
重载:可以指定过滤条件来获取符合条件的祖先元素。例如:
1.
Ancestors(XName name):返回符合特定名称的祖先元素。2.
Ancestors(XName name, XNamespace ns):返回符合特定名称和命名空间的祖先元素。null。重载:可以为不同类型的属性赋值。例如:
1.
SetAttributeValue(XName name, object value):设置属性的值。重载:可以设置不同类型的值。例如:
1.
SetElementValue(XName name, object value):为指定名称的子元素设置值。这些方法是后续进行增删改查的基础,需对其有清楚的理解。
创建XML
通过链式调用,能够让代码更加紧凑和易读,例如:
var contact =new XElement(\"Contact\", new XElement(\"Name\", \"Patrick Hines\"), new XElement(\"Age\",\"22\") new XElement(\"Phone\", \"206-555-0144\", new XAttribute(\"Type\", \"Home\")), new XElement(\"Phone\", \"425-555-0145\", new XAttribute(\"Type\", \"Work\")), new XElement(\"Address\", new XElement(\"City\", \"Mercer Island\"), new XElement(\"State\", \"WA\"), ));
生成的XML:
<Contact> <Name>Patrick Hines</Name> <Age>22</Age> <Phone Type=\"Home\">206-555-0144</Phone> <Phone Type=\"Work\">425-555-0145</phone> <Address> <City>Mercer Island</City> <State>WA</State> </Address></Contact>
查询:LINQ 查询语法(Query Syntax)
官方查询用例文档
LINQ 查询语法类似于 SQL 查询,它是更加直观、声明性的查询方式。如果你有SQL语句基础,那么应该很容易掌握复杂查询的方法。
以上文的XML为例,要查询Type为Home的:
XElement root = XElement.Load(\"Contact.xml\");IEnumerable<XElement> phone = from el in root.Elements(\"Phone\") where (string)el.Attribute(\"Type\") == \"Home\" select el;foreach (XElement el in address) Console.WriteLine(el);
其中用到了Elements(string n)方法来获取所有n标签。
⚠️再次强调:
el.Attribute(\"Type\")返回值是XAttribute,将其强制转换为string是安全的,因为其实现了向基础类型的类型转换操作符。
排序、中间值
假设XML有多个
元素节点,内容均为随机值。
借助于LINQ 查询语法,很容易对其结果根据年龄进行排序:
IEnumerable<XElement> sortedContacts = from contact in root.Elements(\"Contact\")orderby (int)contact.Element(\"Age\")select contact;
计算中间值的例子,实现根据年龄的奇偶性进行排序:
IEnumerable<XElement> sortedContacts = from contact in root.Elements(\"Contact\")let isOdd = (int)contact.Element(\"Age\") % 2orderby isOddselect contact;
let用于在查询中创建一个新的临时变量,保存计算结果以便后续使用。
可以在let中进行更复杂的运算,例如实现选择后面紧接 ul 元素的 p 元素:
IEnumerable<XElement> items = from e in doc.Descendants(\"p\") let z = e.ElementsAfterSelf().FirstOrDefault() where z != null && z.Name.LocalName == \"ul\" select e;用到了更多的新方法,
Descendants、ElementsAfterSelf、FirstOrDefault,但核心原理依旧是在where中进行数值判断。
其中LocalName是XName的一个属性,其获取的是没有命名空间限定符的元素节点的名称。例如: 对于下面的XML文件:<Root xmlns=\"http://www.4399.com\"> <Child>content</Child></Root>其Root的
Name为:{http://www.4399.com}Root
其Root的Name.LocalName为:Root
其Root的Name.NameSpace为:http://www.4399.com
编辑XML
使用LINQ to XML编辑XML非常容易,只需要调用XElement的方法:
增
这些方法都支持多个重载,以调用XElement.Add(…)为例:
var srcTree = new XElement(\"Root\", new XElement(\"Element1\", 1), new XElement(\"Element2\", 2),);var xmlTree = new XElement(\"Root\", new XElement(\"Child1\", 1), new XElement(\"Child2\", 2),);//添加单个元素xmlTree.Add(new XElement(\"NewChild\", \"new content\"));//添加集合xmlTree.Add( from el in srcTree.Elements() where (int)el > 3 select el);//什么也不会做xmlTree.Add(srcTree.Element(\"这是一个不可能找到的元素\"));
删
有些元素有特殊的删除方法:
其中SetAttributeValue、SetElementValue是其函数的特殊用法。
XNode、XContainer 均包括
XElement与XDocument
下面是一个官方的案例:
XElement root = XElement.Parse(@\" \");root.Element(\"Child1\").Element(\"GrandChild1\").Remove();root.Element(\"Child2\").Elements().ToList().Remove();root.Element(\"Child3\").Elements().Remove();
它将从 Child1 中删除第一个子元素,从 Child2 和 Child3 中删除所有子元素。
注意其中使用了ToList(),依然能够删除root元素中的数据。
改
下面的方法修改 XElement:
下面的方法修改 XAttribute:
下面的方法修改 XNode、XContainer(包括 XElement 或 XDocument):
XMLReader/XMLWriter
XmlReader / XmlWriterLINQ to XML在理解XML流式序列化的逻辑之前你需要理解XML结构
XML结构
XmlNodeType 微软官方文档
XmlNodeType 是一个枚举类型,定义了 XmlReader 处理的 XML 文档中可能出现的节点类型。每个节点类型代表 XML 文档结构的一部分,例如元素、属性、文本等。以下是 XmlNodeType 的详细说明:
Element✨)Attribute✨id=\'123\')Attribute不被视为子节点)Text✨)#))))))xml:space=\"preserve\"范围内标记之间的空白))。XmlDeclaration节点必须是文档中的第一个节点。它不能有子节点。它是文档节点的子节点。它可以有提供版本和编码信息的属性
其中最常用的节点我使用✨将其标识。
常见的节点类型与示例:
假设我们有如下的 XML:
<book title=\"XML Guide\"> <author>John Doe</author> </book>
解析此 XML 时,XmlReader 会处理以下节点类型:
XmlNodeType)XmlDeclarationCommentElementtitle=\"XML Guide\"AttributeJohn DoeText 的内容)。CDATAEndElement使用场景和注意事项:
-
动态检查节点类型:
你可以通过XmlReader.NodeType属性动态检查当前节点的类型。例如:if (reader.NodeType == XmlNodeType.Element) { Console.WriteLine($\"Start element: {reader.Name}\");} else if (reader.NodeType == XmlNodeType.Text) { Console.WriteLine($\"Text content: {reader.Value}\");} -
跳过非内容节点:
你可以使用MoveToContent()跳过注释、空白等非内容节点,直接移动到元素或文本节点。 -
特殊节点处理:
某些类型的节点(如Entity、DocumentFragment等)通常不会直接被XmlReader返回。这些节点更多地用于 DOM 模型,而非流式读取。
MoveTo…()方法
XmlReader 中与节点移动相关的成员函数:
MoveToAttribute()true;否则返回 false。MoveToContent()true;否则返回 false。MoveToContentAsync()MoveToContent() 。该方法返回一个 Task,表示操作是否成功。MoveToElement()true 如果移动成功,false 如果当前节点不是属性节点。MoveToFirstAttribute()false。MoveToNextAttribute()false。详细说明:
-
MoveToAttribute():- 将游标从当前的元素节点移动到指定名称的属性节点。例如,如果当前在
元素节点上,可以使用此方法移动到title或author等属性。 - 如果指定的属性存在,返回
true,否则返回false。
- 将游标从当前的元素节点移动到指定名称的属性节点。例如,如果当前在
-
MoveToContent():- 用于跳过当前的非内容节点(例如,空格、注释等),直接跳转到下一个内容节点(例如元素或文本节点)。它会跳过任何不包含实际数据的节点。
- 如果成功跳转到内容节点,返回
true;如果没有更多的内容节点,返回false。
-
MoveToContentAsync():- 与
MoveToContent()功能相同,但该方法是异步的。它返回一个Task,表示移动是否成功,允许在不阻塞主线程的情况下进行操作。
- 与
-
MoveToElement():- 当游标当前位于属性节点时,调用此方法可以将游标移动回包含该属性的元素节点。返回
true表示成功,否则返回false。
- 当游标当前位于属性节点时,调用此方法可以将游标移动回包含该属性的元素节点。返回
-
MoveToFirstAttribute():- 如果当前元素节点包含属性,调用此方法会将游标移动到第一个属性节点。如果元素没有任何属性,返回
false。
- 如果当前元素节点包含属性,调用此方法会将游标移动到第一个属性节点。如果元素没有任何属性,返回
-
MoveToNextAttribute():- 在当前元素节点的属性列表中,调用此方法会将游标移动到下一个属性节点。如果没有更多的属性,返回
false。
- 在当前元素节点的属性列表中,调用此方法会将游标移动到下一个属性节点。如果没有更多的属性,返回
这些方法在 XML 文档的解析中非常有用,可以帮助我们在元素和属性之间导航,灵活地获取不同类型的数据。
ReadTo…()方法
XmlReader 中的 ReadToDescendant、ReadToFollowing 和 ReadToNextSibling 是用于导航和移动游标到 XML 文档中不同节点的方法。它们允许我们跳过当前节点,直接跳到文档中的其他节点。:
ReadToDescendant()false,否则返回 true。ReadToFollowing()false。ReadToNextSibling()false。详细说明:
-
ReadToDescendant():- 用途:用于将游标直接移动到当前节点的第一个子孙元素节点。如果当前节点有子孙元素,这个方法会跳过所有非元素节点(如文本节点、注释节点等),并停留在第一个子孙元素节点上。
- 返回值:如果成功找到子孙元素节点,返回
true;如果当前节点没有子孙元素,返回false。 - 适用场景:这个方法特别适用于你想要快速跳到某个元素的嵌套子元素时。例如,在遍历复杂的 XML 文档时,你可以使用
ReadToDescendant跳过当前节点,直接进入它的第一个子元素。
示例:
// 假设当前在 节点上,下面的 XML 结构中有子孙元素和 。 if (reader.ReadToDescendant(\"title\")) { // 现在游标已经移动到节点。 string title = reader.ReadElementContentAsString(); Console.WriteLine(\"Title: \" + title);} -
ReadToFollowing():- 用途:将游标移动到当前节点之后的下一个符合条件的元素节点。它跳过所有的非元素节点,直到找到下一个元素节点。可以选择指定元素名称,跳到特定类型的节点。
- 返回值:如果成功找到下一个元素节点,返回
true;如果找不到符合条件的节点,返回false。 - 适用场景:当你想跳到文档中某个特定位置并继续解析后续元素时,可以使用此方法。你可以指定一个特定的节点名称,跳到下一个匹配的元素。
示例:
// 假设我们当前在 节点中,下面有和 节点。 // 使用 ReadToFollowing 来跳到下一个符合条件的元素if (reader.ReadToFollowing(\"author\")) { // 游标已经移动到 节点 string author = reader.ReadElementContentAsString(); Console.WriteLine(\"Author: \" + author);} -
ReadToNextSibling():- 用途:将游标移动到当前节点的下一个兄弟节点。兄弟节点是指当前节点同一级别上的其他节点。例如,在父节点
下的兄弟节点可以是、等。 - 返回值:如果当前节点有下一个兄弟节点,返回
true;如果没有兄弟节点,返回false。 - 适用场景:当你需要遍历同一级别的多个兄弟节点时,可以使用
ReadToNextSibling()。这在解析具有平行结构的 XML 时特别有用。
示例:
// 假设我们当前在节点 // 移动到下一个兄弟节点 if (reader.ReadToNextSibling(\"author\")) { string author = reader.ReadElementContentAsString(); Console.WriteLine(\"Author: \" + author);} - 用途:将游标移动到当前节点的下一个兄弟节点。兄弟节点是指当前节点同一级别上的其他节点。例如,在父节点
总结对比:
ReadToDescendant()true 或 falseReadToFollowing()true 或 falseReadToNextSibling()true 或 false这些方法帮助我们在遍历 XML 文档时,灵活地从当前节点跳转到其他相关节点,避免手动处理节点的复杂层次。
以下是 XmlReader 类成员函数的表格,包括函数名和介绍:
Read()true,否则返回 false,直到文档结束。ReadStartElement()ReadEndElement()ReadElementContentAsString()ReadInnerXml()ReadOuterXml()GetAttribute()null。MoveToAttribute()true 如果该属性存在,false 如果该属性不存在。MoveToElement()MoveToFirstAttribute()false。MoveToNextAttribute()true 如果存在下一个属性节点,false 如果没有。IsStartElement()true 如果是,false 如果不是。IsEndElement()true 如果是,false 如果不是。NodeTypeElement、Text、Attribute 等。NameLocalNameNamespaceURIDepthIsEmptyElement),返回 true 或 false。BaseURIHasValuetrue;否则返回 false。Close()XmlReader 实例,释放相关资源。ReadStartElementAsync()ReadEndElementAsync()假设我们有一个简单的 XML 片段
<book> <title lang=\"en\">Introduction to XML</title> <author>John Doe</author></book>
一个伪代码用例为:
// 创建 XmlReader 实例xmlReader = CreateXmlReader(\"books.xml\")// 开始读取文档while (xmlReader.Read()) // 循环直到文件结束 if (xmlReader.IsStartElement()) // 如果当前节点是一个开始元素 if (xmlReader.Name == \"title\") // 检查节点名称是否为 \"title\" title = xmlReader.ReadElementContentAsString() // 获取该元素的内容作为字符串 language = xmlReader.GetAttribute(\"lang\") // 获取 \"lang\" 属性的值 print(\"Title: \" + title + \", Language: \" + language) else if (xmlReader.Name == \"author\") // 检查是否为 \"author\" 元素 author = xmlReader.ReadElementContentAsString() // 获取该元素的内容 print(\"Author: \" + author)// 关闭 XmlReader 实例xmlReader.Close()
结束
以上,即为本章的所有内容。
下一篇文章将详细介绍XMLReader/XMLWriter的使用技巧,以及常见但十分隐蔽的误区。


