System.Xml.XmlReader 类

本文提供了此 API 参考文档的补充说明。

XmlReader 提供对文档或流中 XML 数据的仅正向只读访问权限。 此类符合 W3C 可扩展标记语言 (XML) 1.0(第四版)XML 1.0 中的命名空间(第三版)建议。

XmlReader 方法允许你浏览 XML 数据并读取节点的内容。 类的属性反映当前节点的值,即读取器所在的位置。 ReadState 属性值指示 XML 读取器的当前状态。 例如,属性由 XmlReader.Read 方法设置为 ReadState.Initial,由 XmlReader.Close 方法设置为 ReadState.ClosedXmlReader 还提供针对 DTD 或架构的数据合规性检查和验证。

XmlReader 使用拉取模型来检索数据。 此模型:

  • 通过自然的、由上而下的过程优化来简化状态管理。
  • 支持多个输入流和分层。
  • 使客户端能够向分析器提供直接写入字符串的缓冲区,从而避免了额外的字符串复制。
  • 支持选择性处理。 客户端可以跳过一些项目,并处理应用程序感兴趣的项目。 你还可以提前设置属性来管理 XML 流的处理方式(例如规范化)。

创建 XML 读取器

使用 Create 方法创建 XmlReader 实例。

尽管 .NET 提供了 XmlReader 类(例如 XmlTextReaderXmlNodeReaderXmlValidatingReader 类)的具体实现,但我们建议仅在以下情况下使用这些专用类:

  • 如果要从 XmlNode 对象读取 XML DOM 子树,请使用 XmlNodeReader 类。 (但是,此类不支持 DTD 或架构验证。)
  • 如果必须根据请求扩展实体,且不希望文本内容规范化,或者不希望返回默认特性,请使用 XmlTextReader 类。

若要指定要在 XML 读取器上启用的功能集,请将 System.Xml.XmlReaderSettings 对象传递给 Create 方法。 你可以使用单个 System.Xml.XmlReaderSettings 对象创建多个具有相同功能的读取器,或修改 System.Xml.XmlReaderSettings 对象来创建一个具有不同功能集的新读取器。 还可以轻松将功能添加到现有读取器。

如果你不使用 System.Xml.XmlReaderSettings 对象,系统将使用默认设置。 有关详细信息,请参阅 Create 参考页面。

XmlReader 会在发生 XML 分析错误时引发 XmlException。 引发异常后,读取器的状态不可预测。 例如,报告的节点类型可能与当前节点的实际节点类型不同。 请使用 ReadState 属性检查读取器是否处于错误状态。

验证 XML 数据

若要定义 XML 文档的结构及其元素关系、数据类型和内容约束,请使用文档类型定义 (DTD) 或 XML 架构定义语言 (XSD) 架构。 如果 XML 文档满足 W3C XML 1.0 建议定义的所有语法要求,则认为 XML 文档的格式是标准的。 如果它不仅格式标准,而且符合 DTD 或架构所定义的约束,则视为有效。 (请参阅 W3C XML 架构第 1 部分:结构W3C XML 架构第 2 部分:数据类型建议。)因此,尽管所有有效 XML 文档的格式都是标准的,但并非所有格式标准的 XML 文档都是有效的。

你可以针对 DTD、内联 XSD 架构或存储在 XmlSchemaSet 对象中的 XSD 架构(缓存)验证数据;这些情况在 Create 参考页面上都有介绍。 XmlReader 不支持 XML 数据缩减 (XDR) 架构验证。

请在 XmlReaderSettings 类上使用以下设置来指定 XmlReader 实例支持的验证类型(如果有)。

使用此 XmlReaderSettings 成员 若要指定
DtdProcessing 属性 是否允许 DTD 处理。 默认设置为不允许 DTD 处理。
ValidationType 属性 读取器是否应验证数据,以及要执行的验证类型(DTD 或架构)。 默认设置为无数据验证。
ValidationEventHandler 事件 用于接收有关验证事件的信息的事件处理程序。 如果未提供事件处理程序,在出现第一个验证错误时将引发 XmlException
ValidationFlags 属性 其他验证选项(通过 XmlSchemaValidationFlags 枚举成员):

- AllowXmlAttributes-- 允许在实例文档中使用 XML 特性 (xml:*),即使架构中未定义这些特性。 属性基于其数据类型进行验证。 有关要在特定情况下使用的设置,请参阅 XmlSchemaValidationFlags 参考页面。 (默认禁用。)
- ProcessIdentityConstraints -- 处理在验证期间遇到的标识约束(xs:IDxs:IDREFxs:keyxs:keyrefxs:unique)。 (默认启用。)
- ProcessSchemaLocation --处理由 xsi:schemaLocationxsi:noNamespaceSchemaLocation 特性指定的架构。 (默认启用。)
- ProcessInlineSchema-- 在验证过程中处理内联 XML 架构。 (默认禁用。)
- ReportValidationWarnings--如果发生验证警告,则报告事件。 如果没有验证特定元素或属性时所依据的 DTD 或 XML 架构,通常会发出警告。 ValidationEventHandler 用于通知。 (默认禁用。)
Schemas 用于验证的 XmlSchemaSet
XmlResolver 属性 用于解析和访问外部资源的 XmlResolver。 这可以包括外部实体(如 DTD 和架构),以及 XML 架构中包含的任何 xs:includexs:import 元素。 如果未指定 XmlResolver,则 XmlReader 将使用没有用户凭据的默认 XmlUrlResolver

数据合规性

默认情况下,Create 方法创建的 XML 读取器满足以下合规性要求:

  • 新行和特性值将根据 W3C XML 1.0 建议进行规范化。

  • 所有实体都会自动展开。

  • 即使在读取器不进行验证的情况下,也一定会添加文档类型定义中声明的默认特性。

  • 允许声明映射到正确的 XML 命名空间 URI 的 XML 前缀。

  • 单个 NotationType 特性声明中的表示法名称和单个 Enumeration 特性声明中的 NmTokens 是不同的。

请使用以下 XmlReaderSettings 属性指定要启用的合规性检查的类型:

使用此 XmlReaderSettings 属性 若要 默认值
CheckCharacters 属性 启用或禁用以下检查:

- 字符是否均在合法 XML 字符范围内,定义见“W3C XML 1.0 建议”的 2.2 字符部分。
- 是否所有 XML 名称都有效,定义见“W3C XML 1.0 建议”的 2.3 通用语法构造部分。

当此属性设置为 true(默认值)时,如果 XML 文件包含非法字符或无效的 XML 名称(例如,元素名称以数字开头),则会引发 XmlException 异常。
启用字符和名称检查。

CheckCharacters 设置为 false 会禁用对字符实体引用的字符检查。 如果读取器正在处理文本数据,则无论此设置如何,都一定会检查 XML 名称是否有效。 注意:如果存在 DTD,XML 1.0 建议将要求达到文档级别的合规性。 因此,如果读取器配置为支持 ConformanceLevel.Fragment,但 XML 数据包含文档类型定义 (DTD),则会引发 XmlException
ConformanceLevel 属性 选择要强制执行的合规性级别:

- Document。 符合格式标准的 XML 1.0 文档适用的规则。
- Fragment。 符合格式标准的文档片段适用的规则,这些片段可以用作外部分析实体
- Auto。符合读取器决定的级别。

如果数据不合规,则会引发 XmlException 异常。
Document

当前节点是读取器当前所处的 XML 节点。 所有 XmlReader 方法都执行与此节点相关的操作,并且所有 XmlReader 属性都反映当前节点的值。

通过以下方法,可以轻松浏览节点和分析数据。

使用此 XmlReaderSettings 方法 若要
Read 读取第一个节点,然后每次前进一个节点来读取整个流。 此类调用通常在 while 循环内执行。

使用 NodeType 属性获取当前节点的类型(例如特性、注释、元素等)。
Skip 跳过当前节点的子级并移动到下一个节点。
MoveToContentMoveToContentAsync 跳过非内容节点并移动到下一个内容节点或文件末尾。

非内容节点包括 ProcessingInstructionDocumentTypeCommentWhitespaceSignificantWhitespace

内容节点包括不含空格的文本、CDATAEntityReferenceEndEntity
ReadSubtree 读取元素及其所有子级,并返回设置为 ReadState.Initial 的新 XmlReader 实例。

此方法在围绕 XML 元素创建边界时非常有用;例如,如果希望将数据传递到另一个组件来进行处理,并且希望限制该组件可以访问的数据量。

有关在读取整个文本流时一次前进一个节点并显示每个节点的类型的示例,请参阅 XmlReader.Read 参考页面。

以下部分介绍如何读取特定类型的数据,例如元素、特性和类型化数据。

读取 XML 元素

下表列出了 XmlReader 类为元素处理提供的方法和属性。 在 XmlReader 置于某个元素上之后,节点属性(例如 Name)将反映元素的值。 除了下面所述的成员之外,XmlReader 类的任何常规方法和属性也可以用于处理元素。 例如,可以使用 ReadInnerXml 方法读取元素的内容。

注意

有关开始标记、结束标记和空元素标记的定义,请参阅 W3C XML 1.0 建议的第 3.1 部分。

使用此 XmlReader 成员 若要
IsStartElement 方法 检查当前节点是否为开始标记或空元素标记。
ReadStartElement 方法 检查当前节点是否为元素,并使读取器前进到下一个节点(调用 IsStartElement,后跟 Read)。
ReadEndElement 方法 检查当前节点是否为结束标记,并使读取器前进到下一个节点。
ReadElementString 方法 读取纯文本元素。
ReadToDescendant 方法 使 XML 读取器前进到具有指定名称的下一个后代(子级)元素。
ReadToNextSibling 方法 使 XML 读取器前进到具有指定名称的下一个同级元素。
IsEmptyElement 属性 检查当前元素是否具有结束元素标记。 例如:

- <item num="123"/>IsEmptyElementtrue。)
- <item num="123"> </item>IsEmptyElementfalse,但元素的内容为空。)

有关读取元素文本内容的示例,请参阅 ReadString 方法。 以下示例使用 while 循环处理元素。

while (reader.Read()) {
  if (reader.IsStartElement()) {
    if (reader.IsEmptyElement)
                {
                    Console.WriteLine("<{0}/>", reader.Name);
                }
                else {
      Console.Write("<{0}> ", reader.Name);
      reader.Read(); // Read the start tag.
      if (reader.IsStartElement())  // Handle nested elements.
        Console.Write("\r\n<{0}>", reader.Name);
      Console.WriteLine(reader.ReadString());  //Read the text content of the element.
    }
  }
}
While reader.Read()
  If reader.IsStartElement() Then
    If reader.IsEmptyElement Then
      Console.WriteLine("<{0}/>", reader.Name)
    Else
      Console.Write("<{0}> ", reader.Name)
      reader.Read() ' Read the start tag.
      If reader.IsStartElement() Then ' Handle nested elements.
        Console.Write(vbCr + vbLf + "<{0}>", reader.Name)
      End If
      Console.WriteLine(reader.ReadString()) 'Read the text content of the element.
    End If
  End If
End While

读取 XML 特性

XML 特性通常存在于元素上,但也可以处于 XML 声明和文档类型节点上。

当处于元素节点上时,MoveToAttribute 方法允许你浏览元素的特性列表。 请注意,调用 MoveToAttribute 后,节点属性(如 NameNamespaceURIPrefix)将反映该特性的属性,而不是该特性所属的元素的属性。

XmlReader 类提供可以下方法和属性,用以读取和处理元素上的特性。

使用此 XmlReader 成员 功能
HasAttributes 属性 检查当前节点是否有任何特性。
AttributeCount 属性 获取当前元素上的特性数。
MoveToFirstAttribute 方法 移动到元素中的第一个特性。
MoveToNextAttribute 方法 移动到元素中的下一个特性。
MoveToAttribute 方法 移动到指定的特性。
GetAttribute 方法或 Item[] 属性 获取指定特性的值。
IsDefault 属性 检查当前节点是否是根据 DTD 或架构中定义的默认值生成的特性。
MoveToElement 方法 移动到拥有当前特性的元素。 使用此方法可在浏览一个元素的特性后返回到该元素。
ReadAttributeValue 方法 将特性值分析为一个或多个 TextEntityReferenceEndEntity 节点。

任何常规的 XmlReader 方法和属性也可以用于处理属性。 例如,在 XmlReader 位于某个属性上之后,NameValue 属性将反映该属性的值。 也可以使用任何内容 Read 方法来获取属性的值。

此示例使用 AttributeCount 属性浏览一个元素上的所有特性。

// Display all attributes.
if (reader.HasAttributes) {
  Console.WriteLine("Attributes of <" + reader.Name + ">");
  for (int i = 0; i < reader.AttributeCount; i++) {
    Console.WriteLine("  {0}", reader[i]);
  }
  // Move the reader back to the element node.
  reader.MoveToElement();
}
' Display all attributes.
If reader.HasAttributes Then
  Console.WriteLine("Attributes of <" + reader.Name + ">")
  Dim i As Integer
  For i = 0 To (reader.AttributeCount - 1)
    Console.WriteLine("  {0}", reader(i))
  Next i
  ' Move the reader back to the element node.
  reader.MoveToElement() 
End If

此示例使用 while 循环中的 MoveToNextAttribute 方法浏览各个特性。

if (reader.HasAttributes) {
  Console.WriteLine("Attributes of <" + reader.Name + ">");
  while (reader.MoveToNextAttribute()) {
    Console.WriteLine(" {0}={1}", reader.Name, reader.Value);
  }
  // Move the reader back to the element node.
  reader.MoveToElement();
}
If reader.HasAttributes Then
  Console.WriteLine("Attributes of <" + reader.Name + ">")
  While reader.MoveToNextAttribute()
    Console.WriteLine(" {0}={1}", reader.Name, reader.Value)
  End While
  ' Move the reader back to the element node.
  reader.MoveToElement()
End If

读取 XML 声明节点上的特性

当 XML 读取器处于 XML 声明节点上时,Value 属性会将版本、独立和编码信息作为单个字符串返回。 由 Create 方法、XmlTextReader 类和 XmlValidatingReader 类创建的 XmlReader 对象会将版本、独立项和编码项作为特性公开。

读取文档类型节点上的特性

当 XML 读取器处于文档类型节点上时,GetAttribute 方法和 Item[] 属性可用于返回 SYSTEM 和 PUBLIC 文本的值。 例如,调用 reader.GetAttribute("PUBLIC") 将返回 PUBLIC 值。

读取处理指令节点上的特性

XmlReader 位于某个处理指令节点上时,Value 属性将返回整个文本内容。 处理指令节点中的项不会被视为特性。 这些项无法通过 GetAttributeMoveToAttribute 方法读取。

读取 XML 内容

XmlReader 类包括以下成员,这些成员从 XML 文件读取内容,并将内容作为字符串值返回。 (若要返回 CLR 类型,请参阅转换为 CLR 类型。)

使用此 XmlReader 成员 功能
Value 属性 获取当前节点的文本内容。 返回的值取决于节点类型;有关详细信息,请参阅 Value 参考页面。
ReadString 方法 以字符串形式获取元素或文本节点的内容。 此方法将在遇到处理指令和注释时停止运行。

有关此方法如何处理特定节点类型的详细信息,请参阅 ReadString 参考页面。
ReadInnerXmlReadInnerXmlAsync 方法 获取当前节点的所有内容(包括标记),但不包括开始和结束标记。 例如,对于:

<node>this<child id="123"/></node>

ReadInnerXml 返回:

this<child id="123"/>
ReadOuterXmlReadOuterXmlAsync 方法 获取当前节点及其子级的所有内容,包括标记以及开始/结束标记。 例如,对于:

<node>this<child id="123"/></node>

ReadOuterXml 返回:

<node>this<child id="123"/></node>

转换为 CLR 类型

你可以使用 XmlReader 类的成员(已在下表中列出)读取 XML 数据并将值作为公共语言运行时 (CLR) 类型而不是字符串返回。 借助这些成员,你能够通过最适合编码任务的表示形式获取值,而无需手动分析或转换字符串值。

  • ReadElementContentAs 方法只能在元素节点类型上调用。 这些方法不能在包含子元素或混合内容的元素上使用。 在调用时,XmlReader 对象读取开始标记,读取元素内容,然后移过结束元素标记。 将忽略处理指令和注释并展开实体。

  • ReadContentAs 方法会读取当前读取器位置的文本内容,并且如果 XML 数据没有任何与之关联的架构或数据类型信息,该方法会将文本内容转换为请求的返回类型。 文本、空白、有效空白和 CDATA 节串联在一起。 系统会跳过注释和处理指令,并自动解析实体引用。

XmlReader 类将使用 W3C XML 架构第 2 部分:数据类型建议中定义的规则。

使用此 XmlReader 方法 返回此 CLR 类型
ReadContentAsBooleanReadElementContentAsBoolean Boolean
ReadContentAsDateTimeReadElementContentAsDateTime DateTime
ReadContentAsDoubleReadElementContentAsDouble Double
ReadContentAsLongReadElementContentAsLong Int64
ReadContentAsIntReadElementContentAsInt Int32
ReadContentAsStringReadElementContentAsString String
ReadContentAsReadElementContentAs 你使用 returnType 参数指定的类型
ReadContentAsObjectReadElementContentAsObject 最合适的类型,由 XmlReader.ValueType 属性指定。 有关映射信息,请参阅 System.Xml 类中的类型支持

如果元素由于其格式而无法轻松转换为 CLR 类型,你可以使用架构映射来确保成功转换。 以下示例使用 .xsd 文件将 hire-date 元素转换为 xs:date 类型,然后使用 ReadElementContentAsDateTime 方法将元素作为 DateTime 对象返回。

输入 (hireDate.xml):

<employee xmlns="urn:empl-hire">
    <ID>12365</ID>
    <hire-date>2003-01-08</hire-date>
    <title>Accountant</title>
</employee>

架构 (hireDate.xsd):

<?xml version="1.0"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="urn:empl-hire" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="employee">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="ID" type="xs:unsignedShort" />
        <xs:element name="hire-date" type="xs:date" />
        <xs:element name="title" type="xs:string" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

代码:

// Create a validating XmlReader object. The schema
// provides the necessary type information.
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.Schemas.Add("urn:empl-hire", "hireDate.xsd");
using (XmlReader reader = XmlReader.Create("hireDate.xml", settings)) {

  // Move to the hire-date element.
  reader.MoveToContent();
  reader.ReadToDescendant("hire-date");

  // Return the hire-date as a DateTime object.
  DateTime hireDate = reader.ReadElementContentAsDateTime();
  Console.WriteLine("Six Month Review Date: {0}", hireDate.AddMonths(6));
}
' Create a validating XmlReader object. The schema 
' provides the necessary type information.
Dim settings As XmlReaderSettings = New XmlReaderSettings()
settings.ValidationType = ValidationType.Schema
settings.Schemas.Add("urn:empl-hire", "hireDate.xsd")
Using reader As XmlReader = XmlReader.Create("hireDate.xml", settings) 
  ' Move to the hire-date element.
  reader.MoveToContent()
  reader.ReadToDescendant("hire-date")

  ' Return the hire-date as a DateTime object.
  Dim hireDate As DateTime = reader.ReadElementContentAsDateTime()
  Console.WriteLine("Six Month Review Date: {0}", hireDate.AddMonths(6))
End Using

输出:

Six Month Review Date:  7/8/2003 12:00:00 AM

异步编程

大多数 XmlReader 方法都有异步对应项,而在其方法名称的末尾带有“Async”。 例如,ReadContentAsObject 的异步等效项为 ReadContentAsObjectAsync

以下方法可用于异步方法调用:

以下部分介绍了没有异步对应项的方法的异步用法。

ReadStartElement 方法

public static async Task ReadStartElementAsync(this XmlReader reader, string localname, string ns)
{
    if (await reader.MoveToContentAsync() != XmlNodeType.Element)
    {
        throw new InvalidOperationException(reader.NodeType.ToString() + " is an invalid XmlNodeType");
    }
    if ((reader.LocalName == localname) && (reader.NamespaceURI == ns))
    {
        await reader.ReadAsync();
    }
    else
    {
        throw new InvalidOperationException("localName or namespace doesn’t match");
    }
}
<Extension()>
Public Async Function ReadStartElementAsync(reader As XmlReader, localname As String, ns As String) As Task
    If (Await reader.MoveToContentAsync() <> XmlNodeType.Element) Then
        Throw New InvalidOperationException(reader.NodeType.ToString() + " is an invalid XmlNodeType")
    End If

    If ((reader.LocalName = localname) And (reader.NamespaceURI = ns)) Then
        Await reader.ReadAsync()
    Else
        Throw New InvalidOperationException("localName or namespace doesn’t match")
    End If
End Function

ReadEndElement 方法

public static async Task ReadEndElementAsync(this XmlReader reader)
{
    if (await reader.MoveToContentAsync() != XmlNodeType.EndElement)
    {
        throw new InvalidOperationException();
    }
    await reader.ReadAsync();
}
<Extension()>
Public Async Function ReadEndElementAsync(reader As XmlReader) As task
    If (Await reader.MoveToContentAsync() <> XmlNodeType.EndElement) Then
        Throw New InvalidOperationException()
    End If
    Await reader.ReadAsync()
End Function

ReadToNextSibling 方法

public static async Task<bool> ReadToNextSiblingAsync(this XmlReader reader, string localName, string namespaceURI)
{
    if (localName == null || localName.Length == 0)
    {
        throw new ArgumentException("localName is empty or null");
    }
    if (namespaceURI == null)
    {
        throw new ArgumentNullException("namespaceURI");
    }

    // atomize local name and namespace
    localName = reader.NameTable.Add(localName);
    namespaceURI = reader.NameTable.Add(namespaceURI);

    // find the next sibling
    XmlNodeType nt;
    do
    {
        await reader.SkipAsync();
        if (reader.ReadState != ReadState.Interactive)
            break;
        nt = reader.NodeType;
        if (nt == XmlNodeType.Element &&
             ((object)localName == (object)reader.LocalName) &&
             ((object)namespaceURI ==(object)reader.NamespaceURI))
        {
            return true;
        }
    } while (nt != XmlNodeType.EndElement && !reader.EOF);
    
    return false;
}
<Extension()>
Public Async Function ReadToNextSiblingAsync(reader As XmlReader, localName As String, namespaceURI As String) As Task(Of Boolean)
    If (localName = Nothing Or localName.Length = 0) Then
        Throw New ArgumentException("localName is empty or null")
    End If

    If (namespaceURI = Nothing) Then
        Throw New ArgumentNullException("namespaceURI")
    End If

    ' atomize local name and namespace
    localName = reader.NameTable.Add(localName)
    namespaceURI = reader.NameTable.Add(namespaceURI)

    ' find the next sibling
    Dim nt As XmlNodeType
    Do

        Await reader.SkipAsync()
        If (reader.ReadState <> ReadState.Interactive) Then
            Exit Do
        End If
        nt = reader.NodeType
        If ((nt = XmlNodeType.Element) And
           ((CObj(localName) = CObj(reader.LocalName))) And
           (CObj(namespaceURI) = CObj(reader.NamespaceURI))) Then
            Return True
        End If
    Loop While (nt <> XmlNodeType.EndElement And (Not reader.EOF))

    Return False

End Function

ReadToFollowing 方法

public static async Task<bool> ReadToFollowingAsync(this XmlReader reader, string localName, string namespaceURI)
{
    if (localName == null || localName.Length == 0)
    {
        throw new ArgumentException("localName is empty or null");
    }
    if (namespaceURI == null)
    {
        throw new ArgumentNullException("namespaceURI");
    }

    // atomize local name and namespace
    localName = reader.NameTable.Add(localName);
    namespaceURI = reader.NameTable.Add(namespaceURI);

    // find element with that name
    while (await reader.ReadAsync())
    {
        if (reader.NodeType == XmlNodeType.Element && ((object)localName == (object)reader.LocalName) && ((object)namespaceURI == (object)reader.NamespaceURI))
        {
            return true;
        }
    }
    return false;
}
<Extension()>
Public Async Function ReadToFollowingAsync(reader As XmlReader, localName As String, namespaceURI As String) As Task(Of Boolean)
    If (localName = Nothing Or localName.Length = 0) Then
        Throw New ArgumentException("localName is empty or null")
    End If

    If (namespaceURI = Nothing) Then
        Throw New ArgumentNullException("namespaceURI")
    End If

    ' atomize local name and namespace
    localName = reader.NameTable.Add(localName)
    namespaceURI = reader.NameTable.Add(namespaceURI)

    ' find element with that name
    While (Await reader.ReadAsync())
        If ((reader.NodeType = XmlNodeType.Element) And
           (CObj(localName) = CObj(reader.LocalName)) And
           (CObj(namespaceURI) = CObj(reader.NamespaceURI))) Then
            Return True
        End If
    End While

    Return False
End Function

ReadToDescendant 方法

public static async Task<bool> ReadToDescendantAsync(this XmlReader reader, string localName, string namespaceURI)
{
    if (localName == null || localName.Length == 0)
    {
        throw new ArgumentException("localName is empty or null");
    }
    if (namespaceURI == null)
    {
        throw new ArgumentNullException("namespaceURI");
    }
    // save the element or root depth
    int parentDepth = reader.Depth;
    if (reader.NodeType != XmlNodeType.Element)
    {
        // adjust the depth if we are on root node
        if (reader.ReadState == ReadState.Initial)
        {
            parentDepth--;
        }
        else
        {
            return false;
        }
    }
    else if (reader.IsEmptyElement)
    {
        return false;
    }

    // atomize local name and namespace
    localName = reader.NameTable.Add(localName);
    namespaceURI = reader.NameTable.Add(namespaceURI);

    // find the descendant
    while (await reader.ReadAsync() && reader.Depth > parentDepth)
    {
        if (reader.NodeType == XmlNodeType.Element && ((object)localName == (object)reader.LocalName) && ((object)namespaceURI == (object)reader.NamespaceURI))
        {
            return true;
        }
    }
    return false;
}
<Extension()>
Public Async Function ReadToDescendantAsync(reader As XmlReader, localName As String, namespaceURI As String) As Task(Of Boolean)
    If (localName = Nothing Or localName.Length = 0) Then
        Throw New ArgumentException("localName is empty or null")
    End If

    If (namespaceURI = Nothing) Then
        Throw New ArgumentNullException("namespaceURI")
    End If

    ' save the element or root depth
    Dim parentDepth As Integer = reader.Depth
    If (reader.NodeType <> XmlNodeType.Element) Then
        ' adjust the depth if we are on root node
        If (reader.ReadState = ReadState.Initial) Then
            parentDepth -= 1
        Else
            Return False
        End If
    ElseIf (reader.IsEmptyElement) Then
        Return False
    End If
    ' atomize local name and namespace
    localName = reader.NameTable.Add(localName)
    namespaceURI = reader.NameTable.Add(namespaceURI)

    ' find the descendant
    While (Await reader.ReadAsync() And reader.Depth > parentDepth)
        If (reader.NodeType = XmlNodeType.Element And
           (CObj(localName) = CObj(reader.LocalName)) And
           (CObj(namespaceURI) = CObj(reader.NamespaceURI))) Then
            Return True
        End If
    End While

    Return False
End Function

安全注意事项

使用 XmlReader 类时,请考虑以下事项:

  • XmlReader 引发的异常可能会泄露你不希望在应用中显示的路径信息。 你的应用必须捕获异常并进行适当处理。

  • 如果担心拒绝服务问题或处理的是不受信任的源,请不要启用 DTD 处理。 对于通过 Create 方法创建的 XmlReader 对象,DTD 处理将默认禁用。

    如果启用了 DTD 处理,可以使用 XmlSecureResolver 限制 XmlReader 可以访问的资源。 你还可以对应用进行相应设计,以使 XML 处理受内存和时间的约束。 例如,你可以在 ASP.NET 应用中配置超时限制。

  • XML 数据可以包含对外部资源(例如架构文件)的引用。 默认情况下,外部资源使用没有用户凭据的 XmlUrlResolver 对象进行解析。 通过执行下列操作之一,可以使此操作更加安全:

  • 默认情况下,不会设置 XmlReaderSettings 对象的 ProcessInlineSchemaProcessSchemaLocation 验证标志。 这样,在处理来自不可信的源的 XML 数据时,可以帮助防止 XmlReader 受到基于架构的攻击。 设置了这些标志后,XmlResolver 对象的 XmlReaderSettings 用于在 XmlReader 中解析实例文档中遇到的架构位置。 如果 XmlResolver 属性设置为 null,那么即使设置了 ProcessInlineSchemaProcessSchemaLocation 验证标志,也不会解析架构位置。

    在验证过程中添加的架构会添加新类型,并且可能更改被验证的文档的验证结果。 因此,只应从可信的源解析外部架构。

    在高可用性场景中,当参照对文档的大部分内容具有标识约束的架构验证不受信任的大型 XML 文档时,我们建议禁用 ProcessIdentityConstraints 标志。 默认情况下,此标志处于启用状态。

  • XML 数据可以包含大量属性、命名空间声明、嵌套元素等,需要大量的时间来处理。 若要限制发送到 XmlReader 的输入的大小,你可以:

  • ReadValueChunk 方法可用于处理大型数据流。 此方法一次读取少量的字符,而不是为整个值分配单个字符串。

  • 如果读取的 XML 文档具有大量唯一的本地名称、命名空间或前缀,则可能会出现问题。 如果使用的是从 XmlReader 派生的类,并针对每一项调用了 LocalNamePrefixNamespaceURI 属性,则返回的字符串将添加到 NameTable 中。 NameTable 包含的集合的大小绝不会减小,这会导致字符串句柄的虚拟内存泄漏。 对此,一种缓解措施是从 NameTable 类进行派生并强制实施最大大小配额。 (无法阻止使用 NameTable,也无法在 NameTable 已满时进行切换)。 另一种缓解措施是避免使用提到的属性,而是尽可能将 MoveToAttribute 方法与 IsStartElement 方法结合使用;这些方法不会返回字符串,因此可避免过度填充 NameTable 集合的问题。

  • XmlReaderSettings 对象可以包含敏感信息,例如用户凭据。 不可信的组件可能会使用 XmlReaderSettings 对象及其用户凭据来创建 XmlReader 对象,以读取数据。 缓存 XmlReaderSettings 对象或将 XmlReaderSettings 对象从一个组件传递到另一个组件时,请务必小心谨慎。

  • 不接受来自不可信的源的支持组件,例如 NameTableXmlNamespaceManagerXmlResolver 对象。