LINQ to XML的X部分,在LINQ之路第17讲中是如何讲解的?

2026-05-27 06:451阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2306个文字,预计阅读时间需要10分钟。

LINQ to XML的X部分,在LINQ之路第17讲中是如何讲解的?

.NET Framework 提供了多种操作 XML 数据的 API。自 3.5 版本起,最重要的用途是处理 XML 文档的技术——LINQ to XML。LINQ to XML 包含一个轻量级的 XML 文档对象模型和一组查询运算符,用于简化 XML 文档的处理。

.NET Framework提供了数种操作XML数据的API。从Framework 3.5开始,最重要的用来处理XML文档的技术当属LINQ to XML。LINQ to XML由一个轻量级的XML文档对象模型和一组补充查询运算符组成,并且,该文档对象模型是LINQ友好的。多数情况下,它可以完全取代XML技术的前身:符合W3C规则的DOM,如XmlDocument。现在,就让我们一起开始LINQ to XML的学习之旅,看看它是怎样简化XML的查询与操作,提高我们的工作效率的。

所有的LINQ to XML类型都定义在System.Xml.Linq命名空间中,请记住在运行相关示例时导入该命名空间。

LINQ to XML的X部分,在LINQ之路第17讲中是如何讲解的?

架构预览 认识DOM

让我们先来简单认识一下DOM的概念,然后再来解释LINQ to XML’s DOM背后的基本原理。

考虑下面的XML文件:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<customer id="123" status="archived">
<firstname>Joe</firstname>
<lastname>Bloggs</lastname>
</customer>

和所有的XML文件一样,我们从一个XML声明开始,然后是root元素,元素名为customer。它有2个attributes,名字分别为id和status,值分别为”123”和”archived”。在customer元素之内,包含了两个子元素firstname和lastname,分别包含一个文本内容 ("Joe"和"Bloggs")。

上面的每一种结构:declaration, element, attribute, value, 和text content,都可以用一个相应的类来表示。如果为这些类加上某些集合属性来存储子内容,那么我们就可以通过一个对象树来完整的描述一个文档,这就是文档对象模型,简称DOM。

LINQ to XML文档对象模型(DOM)

LINQ to XML由下面两部分内容组成:

  • 一个XML DOM,我们可以称为X-DOM
  • 一组(大约10个)补充查询运算符

X-DOM包含的类型有XDocument、XElement和XAttribute等。有意思的是,X-DOM并不是和LINQ绑定在一起的,我们可以撇开LINQ查询而单独装载、实例化、更新和保存X-DOM类型。相应的,我们可以使用LINQ来查询老的W3C式样的XML类型,当然,这会有很多限制。

X-DOM的重要特征是它是LINQ友好的,这意味着:

  • 它提供了产生IEnumerable sequences的方法,然后我们就可以在sequence之上来建立查询。
  • 它的构造函数允许我们通过一个LINQ数据转换来创建一个X-DOM树
X-DOM介绍

下图显示了X-DOM的核心类型。使用最频繁的类型当属XElement。XObject是该继承层次的根类型;XElement和XDocument是具体的容器类型。

XObject

XObject所有XML数据的抽象基类,它定义了Parent element和XDocument。

XNode

XNode是大部分XML数据的基类(除了XAttribute。一个XNode可以位于一个融合了多种XNode类型的有序集合之中,比如下面的XML:

<data>
Hello world
<subelement1/>
<!--comment-->
<subelement2/>
</data>

在父元素<data>之内,首先是一个XText node (Hello world),然后是一个XElement node,然后是XComment node,最后是第二个XElement node。

尽管一个XNode可以访问它的父元素,但是它并没有子节点/child nodes的概念。子节点由XContainer类提供。XNode除了包括XElement,它还包含了XText、XComment等类型节点(见上图),在后面讲述X-DOM导航时你会看到各种XXXNodes和XXXElements方法,请记得这个区别。

XContainer

XContainer定义了处理子节点的方法,它是XElement和XDocument的抽象基类。

XElement

XElement包含了管理属性的方法,以及Name和Value成员。并且,由于它的基类是XContainer,所以它可以包含子节点。通常情况下,一个element会有单个XText子节点,并且Value属性封装了对该XText子节点的操作。所以,多数情况下,我们并不需要直接和XText打交道。

XDocument

XDocument表示了XML树的根节点。它包装了root XElement、一个XDeclaration、processing instructions、和其他根级类型对象。和W3C DOM不同的是,对XDocument的使用是可选的,我们可以在不创建XDocument的情况下装载、操作和保存一个X-DOM!对XDocument的独立性甚至意味着我们可以高效的把一个node子树移动到另一个不相关的X-DOM层次对象中去。

Loading和Parsing

Xelement和XDocument都提供了静态的Load和Parse方法,他们用来创建X-DOM tree。

  • Load用来从file、URI、Stream、TextReader或XmlReader创建X-DOM
  • Parse用来从string创建X-DOM

示例如下:

XDocument fromWeb = XDocument.Load("albahari.com/sample.xml");
XElement fromFile = XElement.Load(@"e:\media\somefile.xml");
XElement config = XElement.Parse(
@"<configuration>
<client enabled='true'>
<timeout>30</timeout>
</client>
</configuration>"); 保存和序列化

调用任意node的ToString()方法都会把它的内容转换到一个XML string,该string由分行符和缩进格式化,当然我们可以通过SaveOptions.DisableFormatting属性来取消分行符合缩进。

XElement和XDocument还提供了Save方法来把一个X-DOM写到一个file, Stream, TextWriter, 或 XmlWriter。如果指定的是一个file,则会自动添加一个XML declaration。对XML declarations的详细介绍会在之后给出。

实例化X-DOM

除了使用Load和Parse方法,我们还可以通过实例化对象并把他们添加至父节点来创建X-DOM tree。

// 构建XElement和XAttribute时,只需提供name和value
XElement lastName = new XElement("lastname", "Bloggs");
lastName.Add(new XComment("nice name"));

XElement customer = new XElement("customer");
customer.Add(new XAttribute("id", 123));
customer.Add(new XElement("firstname", "Joe"));
customer.Add(lastName);

Console.WriteLine(customer.ToString());

结果如下:

<customer id="123">
<firstname>Joe</firstname>
<lastname>Bloggs<!--nice name--></lastname>
</customer>

创建XElement时,value参数是可选的,我们可以只指定name而在此之后再添加内容。当我们提供了value属性时,一个简单的string就可以了,XDOM会自动将其转换为XText子节点。

函数式构造/Functional Construction

在上一个例子中,我们很难从代码中看出XML的结构。现在,XDOM支持了另外一种实例化模式:函数式构造(由函数式编程而来)。通过函数式构造,我们在一个表达式中就可以创建完整的XDOM tree:

XElement customer =
new XElement("customer", new XAttribute("id", 123),
new XElement("firstname", "joe"),
new XElement("lastname", "bloggs",
new XComment("nice name")
)
);

这样有两个优势。首先,代码反映了XML的结构;其次,它可以与LINQ查询的select子句进行交互。比如,下面的LINQ to SQL查询直接把结果转换到一个X-DOM:

XElement query =
new XElement("customers",
from c in dataContext.Customers
select
new XElement("customer", new XAttribute("id", c.ID),
new XElement("firstname", c.FirstName),
new XElement("lastname", c.LastName,
new XComment("nice name")
)
)
);

这种技术的详细信息会在后续篇章中再作介绍。

函数式构造工作方式

函数式构造之所以可行,是因为XElement和XDocument的构造函数提供了如下的重载形式:

// params object[]意味着可以接受任意数量的参数
public XElement (XName name, params object[] content)
// XContainer的Add方法也是如此:
public void Add (params object[] content)

所以,当我们创建或添加X-DOM时可以指定任意数量任意类型的子对象(child objects)。那么XContainer为何能接受任意类型的子对象呢?要知道原因,我们就需要来查看它们在内部实现中是如何处理每个子对象的。下面就是XContainer类型(XElement和XDocument的基类型)对于子对象的处理方式:

  1. 如果该对象为空,忽略它
  2. 如果该对象基于XNode或XStreamingElement,添加至Nodes集合。
  3. 如果该对象是一个XAttribute,添加至Attributes集合。
  4. 如果是一个string,用XText节点封装该string并添加到Nodes集合。
  5. 如果该对象实现了IEnumerable,则遍历它,上面的规则会应用到其中每一个element。
  6. 否则,该对象会被转换至一个string,经XText节点封装后添加到Nodes集合。

所有的数据最后都会被添加到Nodes或Attributes集合之一,并且,任何对象都是有效的,因为不管怎样,我们都可以调用ToString来把它转换成一个string。

那么上面LINQ查询中的select语句是如何向XElement中添加元素的呢,记住,LINQ查询结果为一IEnumerable<T>(上例T为XElement) sequence,所以应用规则5,遍历该sequence,把每一个XElement都添加至name为customers的XElement之中。

自动完全克隆

当一个node或attribute被添加到某个element后,它的Parent属性就指向了该element。一个node只能有一个parent element。所以当你把一个已经有Parent属性的node添加到另一个element后,该node会被自动深克隆(deep-cloned)。示例如下:

// 每个customer都有address对象的一份拷贝
var address = new XElement("address",
new XElement("street", "Hong mei"),
new XElement("town", "Xu hui")
);

var customer1 = new XElement("customer1", address);
var customer2 = new XElement("customer2", address); // address被同时添加至customer1和customer2
customer1.Element("address").Element("street").Value = "Yi shan"; //修改customer1的address
Console.WriteLine(customer2.Element("address").Element("street").Value); // Hong mei, customer2的address并没有被修改

这种自动复制的技术保证了X-DOM对象实例化时避免了意想不到的副作用。

在下一篇中,我们将会对LINQ to XML的导航查询功能进行详细的介绍。


系列博客导航:

LINQ之路系列博客导航

LINQ之路1:LINQ介绍

LINQ之路2:C# 3.0的语言功能(上)

LINQ之路3:C# 3.0的语言功能(下)

LINQ之路 4:LINQ方法语法

LINQ之路 5:LINQ查询表达式

LINQ之路 6:延迟执行(Deferred Execution)

LINQ之路 7:子查询、创建策略和数据转换

LINQ之路 8:解释查询(Interpreted Queries)

LINQ之路 9:LINQ to SQL 和 Entity Framework(上)

LINQ之路10:LINQ to SQL 和 Entity Framework(下)

LINQ之路11:LINQ Operators之过滤(Filtering)

LINQ之路12:LINQ Operators之数据转换(Projecting)

LINQ之路13:LINQ Operators之连接(Joining)

LINQ之路14:LINQ Operators之排序和分组(Ordering and Grouping)

LINQ之路15:LINQ Operators之元素运算符、集合方法、量词方法

LINQ之路16:LINQ Operators之集合运算符、Zip操作符、转换方法、生成器方法

LINQ之路17:LINQ to XML之X-DOM介绍

LINQ之路18:LINQ to XML之导航和查询

LINQ之路19:LINQ to XML之X-DOM更新、和Value属性交互

LINQ之路20:LINQ to XML之Documents、Declarations和Namespaces

LINQ之路21:LINQ to XML之生成X-DOM(Projecting)

LINQ之路系列博客后记

本文共计2306个文字,预计阅读时间需要10分钟。

LINQ to XML的X部分,在LINQ之路第17讲中是如何讲解的?

.NET Framework 提供了多种操作 XML 数据的 API。自 3.5 版本起,最重要的用途是处理 XML 文档的技术——LINQ to XML。LINQ to XML 包含一个轻量级的 XML 文档对象模型和一组查询运算符,用于简化 XML 文档的处理。

.NET Framework提供了数种操作XML数据的API。从Framework 3.5开始,最重要的用来处理XML文档的技术当属LINQ to XML。LINQ to XML由一个轻量级的XML文档对象模型和一组补充查询运算符组成,并且,该文档对象模型是LINQ友好的。多数情况下,它可以完全取代XML技术的前身:符合W3C规则的DOM,如XmlDocument。现在,就让我们一起开始LINQ to XML的学习之旅,看看它是怎样简化XML的查询与操作,提高我们的工作效率的。

所有的LINQ to XML类型都定义在System.Xml.Linq命名空间中,请记住在运行相关示例时导入该命名空间。

LINQ to XML的X部分,在LINQ之路第17讲中是如何讲解的?

架构预览 认识DOM

让我们先来简单认识一下DOM的概念,然后再来解释LINQ to XML’s DOM背后的基本原理。

考虑下面的XML文件:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<customer id="123" status="archived">
<firstname>Joe</firstname>
<lastname>Bloggs</lastname>
</customer>

和所有的XML文件一样,我们从一个XML声明开始,然后是root元素,元素名为customer。它有2个attributes,名字分别为id和status,值分别为”123”和”archived”。在customer元素之内,包含了两个子元素firstname和lastname,分别包含一个文本内容 ("Joe"和"Bloggs")。

上面的每一种结构:declaration, element, attribute, value, 和text content,都可以用一个相应的类来表示。如果为这些类加上某些集合属性来存储子内容,那么我们就可以通过一个对象树来完整的描述一个文档,这就是文档对象模型,简称DOM。

LINQ to XML文档对象模型(DOM)

LINQ to XML由下面两部分内容组成:

  • 一个XML DOM,我们可以称为X-DOM
  • 一组(大约10个)补充查询运算符

X-DOM包含的类型有XDocument、XElement和XAttribute等。有意思的是,X-DOM并不是和LINQ绑定在一起的,我们可以撇开LINQ查询而单独装载、实例化、更新和保存X-DOM类型。相应的,我们可以使用LINQ来查询老的W3C式样的XML类型,当然,这会有很多限制。

X-DOM的重要特征是它是LINQ友好的,这意味着:

  • 它提供了产生IEnumerable sequences的方法,然后我们就可以在sequence之上来建立查询。
  • 它的构造函数允许我们通过一个LINQ数据转换来创建一个X-DOM树
X-DOM介绍

下图显示了X-DOM的核心类型。使用最频繁的类型当属XElement。XObject是该继承层次的根类型;XElement和XDocument是具体的容器类型。

XObject

XObject所有XML数据的抽象基类,它定义了Parent element和XDocument。

XNode

XNode是大部分XML数据的基类(除了XAttribute。一个XNode可以位于一个融合了多种XNode类型的有序集合之中,比如下面的XML:

<data>
Hello world
<subelement1/>
<!--comment-->
<subelement2/>
</data>

在父元素<data>之内,首先是一个XText node (Hello world),然后是一个XElement node,然后是XComment node,最后是第二个XElement node。

尽管一个XNode可以访问它的父元素,但是它并没有子节点/child nodes的概念。子节点由XContainer类提供。XNode除了包括XElement,它还包含了XText、XComment等类型节点(见上图),在后面讲述X-DOM导航时你会看到各种XXXNodes和XXXElements方法,请记得这个区别。

XContainer

XContainer定义了处理子节点的方法,它是XElement和XDocument的抽象基类。

XElement

XElement包含了管理属性的方法,以及Name和Value成员。并且,由于它的基类是XContainer,所以它可以包含子节点。通常情况下,一个element会有单个XText子节点,并且Value属性封装了对该XText子节点的操作。所以,多数情况下,我们并不需要直接和XText打交道。

XDocument

XDocument表示了XML树的根节点。它包装了root XElement、一个XDeclaration、processing instructions、和其他根级类型对象。和W3C DOM不同的是,对XDocument的使用是可选的,我们可以在不创建XDocument的情况下装载、操作和保存一个X-DOM!对XDocument的独立性甚至意味着我们可以高效的把一个node子树移动到另一个不相关的X-DOM层次对象中去。

Loading和Parsing

Xelement和XDocument都提供了静态的Load和Parse方法,他们用来创建X-DOM tree。

  • Load用来从file、URI、Stream、TextReader或XmlReader创建X-DOM
  • Parse用来从string创建X-DOM

示例如下:

XDocument fromWeb = XDocument.Load("albahari.com/sample.xml");
XElement fromFile = XElement.Load(@"e:\media\somefile.xml");
XElement config = XElement.Parse(
@"<configuration>
<client enabled='true'>
<timeout>30</timeout>
</client>
</configuration>"); 保存和序列化

调用任意node的ToString()方法都会把它的内容转换到一个XML string,该string由分行符和缩进格式化,当然我们可以通过SaveOptions.DisableFormatting属性来取消分行符合缩进。

XElement和XDocument还提供了Save方法来把一个X-DOM写到一个file, Stream, TextWriter, 或 XmlWriter。如果指定的是一个file,则会自动添加一个XML declaration。对XML declarations的详细介绍会在之后给出。

实例化X-DOM

除了使用Load和Parse方法,我们还可以通过实例化对象并把他们添加至父节点来创建X-DOM tree。

// 构建XElement和XAttribute时,只需提供name和value
XElement lastName = new XElement("lastname", "Bloggs");
lastName.Add(new XComment("nice name"));

XElement customer = new XElement("customer");
customer.Add(new XAttribute("id", 123));
customer.Add(new XElement("firstname", "Joe"));
customer.Add(lastName);

Console.WriteLine(customer.ToString());

结果如下:

<customer id="123">
<firstname>Joe</firstname>
<lastname>Bloggs<!--nice name--></lastname>
</customer>

创建XElement时,value参数是可选的,我们可以只指定name而在此之后再添加内容。当我们提供了value属性时,一个简单的string就可以了,XDOM会自动将其转换为XText子节点。

函数式构造/Functional Construction

在上一个例子中,我们很难从代码中看出XML的结构。现在,XDOM支持了另外一种实例化模式:函数式构造(由函数式编程而来)。通过函数式构造,我们在一个表达式中就可以创建完整的XDOM tree:

XElement customer =
new XElement("customer", new XAttribute("id", 123),
new XElement("firstname", "joe"),
new XElement("lastname", "bloggs",
new XComment("nice name")
)
);

这样有两个优势。首先,代码反映了XML的结构;其次,它可以与LINQ查询的select子句进行交互。比如,下面的LINQ to SQL查询直接把结果转换到一个X-DOM:

XElement query =
new XElement("customers",
from c in dataContext.Customers
select
new XElement("customer", new XAttribute("id", c.ID),
new XElement("firstname", c.FirstName),
new XElement("lastname", c.LastName,
new XComment("nice name")
)
)
);

这种技术的详细信息会在后续篇章中再作介绍。

函数式构造工作方式

函数式构造之所以可行,是因为XElement和XDocument的构造函数提供了如下的重载形式:

// params object[]意味着可以接受任意数量的参数
public XElement (XName name, params object[] content)
// XContainer的Add方法也是如此:
public void Add (params object[] content)

所以,当我们创建或添加X-DOM时可以指定任意数量任意类型的子对象(child objects)。那么XContainer为何能接受任意类型的子对象呢?要知道原因,我们就需要来查看它们在内部实现中是如何处理每个子对象的。下面就是XContainer类型(XElement和XDocument的基类型)对于子对象的处理方式:

  1. 如果该对象为空,忽略它
  2. 如果该对象基于XNode或XStreamingElement,添加至Nodes集合。
  3. 如果该对象是一个XAttribute,添加至Attributes集合。
  4. 如果是一个string,用XText节点封装该string并添加到Nodes集合。
  5. 如果该对象实现了IEnumerable,则遍历它,上面的规则会应用到其中每一个element。
  6. 否则,该对象会被转换至一个string,经XText节点封装后添加到Nodes集合。

所有的数据最后都会被添加到Nodes或Attributes集合之一,并且,任何对象都是有效的,因为不管怎样,我们都可以调用ToString来把它转换成一个string。

那么上面LINQ查询中的select语句是如何向XElement中添加元素的呢,记住,LINQ查询结果为一IEnumerable<T>(上例T为XElement) sequence,所以应用规则5,遍历该sequence,把每一个XElement都添加至name为customers的XElement之中。

自动完全克隆

当一个node或attribute被添加到某个element后,它的Parent属性就指向了该element。一个node只能有一个parent element。所以当你把一个已经有Parent属性的node添加到另一个element后,该node会被自动深克隆(deep-cloned)。示例如下:

// 每个customer都有address对象的一份拷贝
var address = new XElement("address",
new XElement("street", "Hong mei"),
new XElement("town", "Xu hui")
);

var customer1 = new XElement("customer1", address);
var customer2 = new XElement("customer2", address); // address被同时添加至customer1和customer2
customer1.Element("address").Element("street").Value = "Yi shan"; //修改customer1的address
Console.WriteLine(customer2.Element("address").Element("street").Value); // Hong mei, customer2的address并没有被修改

这种自动复制的技术保证了X-DOM对象实例化时避免了意想不到的副作用。

在下一篇中,我们将会对LINQ to XML的导航查询功能进行详细的介绍。


系列博客导航:

LINQ之路系列博客导航

LINQ之路1:LINQ介绍

LINQ之路2:C# 3.0的语言功能(上)

LINQ之路3:C# 3.0的语言功能(下)

LINQ之路 4:LINQ方法语法

LINQ之路 5:LINQ查询表达式

LINQ之路 6:延迟执行(Deferred Execution)

LINQ之路 7:子查询、创建策略和数据转换

LINQ之路 8:解释查询(Interpreted Queries)

LINQ之路 9:LINQ to SQL 和 Entity Framework(上)

LINQ之路10:LINQ to SQL 和 Entity Framework(下)

LINQ之路11:LINQ Operators之过滤(Filtering)

LINQ之路12:LINQ Operators之数据转换(Projecting)

LINQ之路13:LINQ Operators之连接(Joining)

LINQ之路14:LINQ Operators之排序和分组(Ordering and Grouping)

LINQ之路15:LINQ Operators之元素运算符、集合方法、量词方法

LINQ之路16:LINQ Operators之集合运算符、Zip操作符、转换方法、生成器方法

LINQ之路17:LINQ to XML之X-DOM介绍

LINQ之路18:LINQ to XML之导航和查询

LINQ之路19:LINQ to XML之X-DOM更新、和Value属性交互

LINQ之路20:LINQ to XML之Documents、Declarations和Namespaces

LINQ之路21:LINQ to XML之生成X-DOM(Projecting)

LINQ之路系列博客后记