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
/ XmlWriter
LINQ 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
)
XmlDeclaration
Comment
Element
title=\"XML Guide\"
Attribute
John Doe
Text
的内容)。
CDATA
EndElement
使用场景和注意事项:
-
动态检查节点类型:
你可以通过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
或 false
ReadToFollowing()
true
或 false
ReadToNextSibling()
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
如果不是。NodeType
Element
、Text
、Attribute
等。Name
LocalName
NamespaceURI
Depth
IsEmptyElement
),返回 true
或 false
。BaseURI
HasValue
true
;否则返回 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的使用技巧,以及常见但十分隐蔽的误区。