方法 : ヘッダー情報にアクセスして XML フラグメントをストリーム出力する
更新 : November 2007
大きな XML ファイルを任意に読み取り、アプリケーションのメモリ使用量を予想できるようにアプリケーションを作成しなければならない場合があります。大きな XML ファイルを XML ツリーに設定しようとすると、ファイルのサイズに比例してメモリが過剰に使用されます。したがって、代わりにストリーミングの手法を使用する必要があります。
これを実現する 1 つの選択肢として、XmlReader を使用してアプリケーションを作成する方法があります。 ただし、場合によっては、XML ツリーに対してクエリを実行するとき、LINQ の使用が必要になることがあります。このような場合は、カスタムの軸メソッドを独自に記述します。詳細については、「方法 : LINQ to XML 軸メソッドを記述する」を参照してください。
独自の軸メソッドを記述するには、XmlReader を使用して、対象となるノードの 1 つに到達するまでノードを読み取る小さなメソッドを記述します。このメソッドから ReadFrom が呼び出され、これにより XmlReader からデータが読み取られ、XML フラグメントがインスタンス化されます。さらに、カスタムの軸メソッドを列挙するメソッドに対しては、yield return を使用して各フラグメントが生成されます。これで、カスタムの軸メソッド上に LINQ クエリを記述できます。
ストリーミングの手法は、ソース ドキュメントを 1 回だけ処理する必要がある場合に適しており、ドキュメントの順序で要素を処理できます。OrderBy などの一部の標準クエリ演算子では、ソースが反復処理され、すべてのデータが収集され並べ替えられて、最終的にはシーケンス内の最初の項目が生成されます。最初の項目を生成する前にソースを具体化するクエリ演算子を使用すると、メモリ使用量を低く維持することができないので注意してください。
使用例
ストリーム出力は関心の高い問題となる場合があるため、例を使って説明します。次の XML ドキュメントでは、カスタムの軸メソッドのコンシューマが、各項目が属している顧客の名前も認識している必要があります。
<?xml version="1.0" encoding="utf-8" ?>
<Root>
<Customer>
<Name>A. Datum Corporation</Name>
<Item>
<Key>0001</Key>
</Item>
<Item>
<Key>0002</Key>
</Item>
<Item>
<Key>0003</Key>
</Item>
<Item>
<Key>0004</Key>
</Item>
</Customer>
<Customer>
<Name>Fabrikam, Inc.</Name>
<Item>
<Key>0005</Key>
</Item>
<Item>
<Key>0006</Key>
</Item>
<Item>
<Key>0007</Key>
</Item>
<Item>
<Key>0008</Key>
</Item>
</Customer>
<Customer>
<Name>Southridge Video</Name>
<Item>
<Key>0009</Key>
</Item>
<Item>
<Key>0010</Key>
</Item>
</Customer>
</Root>
この例で採用している方法では、ヘッダー情報の監視と保存が行われ、その後でヘッダー情報と列挙される詳細情報の両方が含まれている小さな XML ツリーが構築されます。次に、軸メソッドによってこの新しい小さな XML ツリーが生成されます。これでクエリは、詳細情報だけでなくヘッダー情報にもアクセスできるようになります。
この方法で使用されるメモリは少量です。詳細な XML フラグメントが個々に生成されるときに、前のフラグメントへの参照は保持されず、そのフラグメントはガベージ コレクションの対象になります。この手法を使用すると、存続期間の短いオブジェクトがヒープ上に多数作成されるので注意してください。
次の例では、URI で指定されたファイルから XML フラグメントをストリーム出力する、カスタムの軸メソッドを実装して使用する方法を示します。このカスタムの軸は、Customer、Name、Item の各要素を含んだドキュメントを前提として記述されています。また、それらの要素は、上記の Source.xml ドキュメントと同じように配置されます。これは単純な実装です。ただし、より堅牢に実装する場合は、無効なドキュメントの解析にも対応するようにします。
メモ : |
---|
次の例では、C# の yield return 構造を使用します。Visual Basic 2008 には類似した機能がないため、この例は C# のみを対象としています。 |
static IEnumerable<XElement> StreamCustomerItem(string uri)
{
using (XmlReader reader = XmlReader.Create(uri))
{
XElement name = null;
XElement item = null;
reader.MoveToContent();
// Parse the file, save header information when encountered, and yield the
// Item XElement objects as they are created.
// loop through Customer elements
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element
&& reader.Name == "Customer")
{
// move to Name element
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element &&
reader.Name == "Name")
{
name = XElement.ReadFrom(reader) as XElement;
break;
}
}
// loop through Item elements
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.EndElement)
break;
if (reader.NodeType == XmlNodeType.Element
&& reader.Name == "Item")
{
item = XElement.ReadFrom(reader) as XElement;
if (item != null) {
XElement tempRoot = new XElement("Root",
new XElement(name)
);
tempRoot.Add(item);
yield return item;
}
}
}
}
}
}
}
static void Main(string[] args)
{
XElement xmlTree = new XElement("Root",
from el in StreamCustomerItem("Source.xml")
where (int)el.Element("Key") >= 3 && (int)el.Element("Key") <= 7
select new XElement("Item",
new XElement("Customer", (string)el.Parent.Element("Name")),
new XElement(el.Element("Key"))
)
);
Console.WriteLine(xmlTree);
}
このコードを実行すると、次の出力が生成されます。
<Root>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0003</Key>
</Item>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0004</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0005</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0006</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0007</Key>
</Item>
</Root>