次の方法で共有


XmlReader から XML フラグメントをストリーム配信する方法 (LINQ to XML)

大きな XML ファイルを処理する必要があるときに、XML ツリー全体をメモリに読み込むことができない場合があります。 この記事では、C# および Visual Basic で XmlReader を使用してフラグメントをストリーム配信する方法を示します。

XmlReader を使用して XElement オブジェクトを読み取るための最も効果的な方法の 1 つは、カスタムの軸メソッドを独自に記述することです。 一般に軸メソッドは、この記事の例で示すように、XElementIEnumerable<T> などのコレクションを返します。 カスタムの軸メソッドでは、ReadFrom メソッドを呼び出して XML フラグメントを作成した後に、yield return を使用してコレクションを返します。 これにより、カスタムの軸メソッドに遅延実行セマンティクスが付加されます。

XmlReader オブジェクトから XML ツリーを作成する場合は、XmlReader を要素に配置する必要があります。 ReadFrom メソッドは、要素の終了タグを読み取るまで制御を戻しません。

部分ツリーを作成する場合は、XmlReader をインスタンス化し、XElement ツリーに変換するノード上にリーダーを配置し、XElement オブジェクトを作成します。

ヘッダー情報にアクセスして XML フラグメントをストリーム出力する方法」という記事には、より複雑なドキュメントをストリーム出力する方法が記載されています。

大きな XML ドキュメントのストリーミング変換を実行する方法」という記事には、LINQ to XML を使用し、メモリ占有領域を低く抑えながら非常に大きな XML ドキュメントを変換する例が記載されています。

例: カスタムの軸メソッドを作成する

次の例では、カスタムの軸メソッドを作成します。 このメソッドに対してクエリを実行するには、LINQ クエリを使用します。 カスタムの軸メソッド StreamRootChildDoc では、繰り返す Child 要素が含まれるドキュメントを読み取ることができます。

using System.Xml;
using System.Xml.Linq;

static IEnumerable<XElement> StreamRootChildDoc(StringReader stringReader)
{
    using XmlReader reader = XmlReader.Create(stringReader);

    reader.MoveToContent();

    // Parse the file and display each of the nodes.
    while (true)
    {
        // If the current node is an element and named "Child"
        if (reader.NodeType == XmlNodeType.Element && reader.Name == "Child")
        {
            // Get the current node and advance the reader to the next
            if (XNode.ReadFrom(reader) is XElement el)
                yield return el;

        }
        else if (!reader.Read())
            break;
    }
}

string markup = """
                <Root>
                  <Child Key="01">
                    <GrandChild>aaa</GrandChild>
                  </Child>
                  <Child Key="02">
                    <GrandChild>bbb</GrandChild>
                  </Child>
                  <Child Key="03">
                    <GrandChild>ccc</GrandChild>
                  </Child>
                </Root>
                """;

IEnumerable<string> grandChildData =
    from el in StreamRootChildDoc(new StringReader(markup))
    where (int)el.Attribute("Key") > 1
    select (string)el.Element("GrandChild");

foreach (string str in grandChildData)
    Console.WriteLine(str);
Imports System.Xml

Module Module1

    Public Iterator Function StreamRootChildDoc(stringReader As IO.StringReader) As IEnumerable(Of XElement)
        Using reader As XmlReader = XmlReader.Create(stringReader)
            reader.MoveToContent()

            ' Parse the file and display each of the nodes.
            While True

                ' If the current node is an element and named "Child"
                If reader.NodeType = XmlNodeType.Element And reader.Name = "Child" Then

                    ' Get the current node and advance the reader to the next
                    Dim el As XElement = TryCast(XNode.ReadFrom(reader), XElement)

                    If (el IsNot Nothing) Then
                        Yield el
                    End If

                ElseIf Not reader.Read() Then
                    Exit While
                End If

            End While
        End Using
    End Function

    Sub Main()

        Dim markup = "<Root>
                       <Child Key=""01"">
                         <GrandChild>aaa</GrandChild>
                       </Child>
                       <Child Key=""02"">
                         <GrandChild>bbb</GrandChild>
                       </Child>
                       <Child Key=""03"">
                         <GrandChild>ccc</GrandChild>
                       </Child>
                     </Root>"

        Dim grandChildData =
             From el In StreamRootChildDoc(New IO.StringReader(markup))
             Where CInt(el.@Key) > 1
             Select el.<GrandChild>.Value

        For Each s In grandChildData
            Console.WriteLine(s)
        Next

    End Sub
End Module

この例を実行すると、次の出力が生成されます。

bbb
ccc

この例で使用されている手法では、Child 要素が大量にある場合でも、メモリ占有領域が少ない状態で維持されます。

関連項目