C# XML Parser入門:XmlReader・XDocument・XmlDocumentの使い方と選び方を徹底解説
はじめに
C#でXMLを解析する方法は一つではありません。.NETには、ストリーム形式で高速に読み取るXmlReader、LINQを利用して直感的に操作できるXDocument、DOMとXPathを中心に扱うXmlDocumentが用意されています。
どのC# XML Parserを選ぶべきかは、XMLファイルのサイズ、読み取りだけか編集も必要か、既存システムとの互換性が必要かによって変わります。小さなXMLを扱うだけなら、どの方法でも大きな問題はありません。しかし、大容量XMLをXDocumentやXmlDocumentで丸ごと読み込むと、メモリ使用量が増えて処理が不安定になる可能性があります。
この記事では、XmlReader・XDocument・XmlDocumentの特徴、基本的な使い方、選び方、実務で役立つコード、エラーやセキュリティ上の注意点を解説します。
1. C#でXMLを扱う前に知っておきたい基礎
1-1. XMLパーサーとは何か
XMLパーサーとは、XML形式の文字列やファイルを読み取り、要素、属性、テキスト、名前空間などの構造をプログラムから扱える形に変換する仕組みです。
例えば、次のXMLにはbook要素、id属性、title要素、price要素が含まれています。
<book id="1"><title>C#入門</title><price>2980</price></book>XMLパーサーを使うと、次のような処理が可能です。
book要素を検索するid属性の値を取得するtitle要素のテキストを読み取るprice要素の値を変更する新しい要素を追加してXMLファイルへ保存する
XMLは単なる文字列ではなく階層構造を持つため、正規表現や文字列検索で解析するのは適切ではありません。C#では、目的に応じたXMLパーサーを使用することが重要です。
1-2. C#でXML解析が必要になる主な場面
C#でXML解析が必要になる代表的な場面には、次のようなものがあります。
外部システムとのデータ連携
SOAP APIのレスポンス解析
設定ファイルの読み書き
RSSやAtomフィードの取得
業務システムから出力されたXML帳票の処理
Officeファイルや各種標準フォーマットの解析
古いシステムから新しいシステムへのデータ移行
XML形式のログファイルの集計
近年はJSONが使われる場面も増えていますが、名前空間、属性、スキーマ、文書構造を重視するシステムではXMLが現在も広く利用されています。
1-3. .NETで使える代表的なXMLパーサーの種類
.NETでXMLを扱う代表的なクラスは次の3種類です。
| クラス | 名前空間 | 主な特徴 |
|---|---|---|
XmlReader | System.Xml | 前方向へ順番に読み取るストリーム型 |
XDocument | System.Xml.Linq | LINQ to XMLを使えるツリー型 |
XmlDocument | System.Xml | DOMとXPathを中心に操作するツリー型 |
XMLをC#オブジェクトへ直接変換したい場合は、XmlSerializerも利用できます。ただし、XmlSerializerはXMLの任意の場所を検索するパーサーというより、XMLとオブジェクトを相互変換するための仕組みです。
1-4. XmlReader・XDocument・XmlDocumentの位置づけ
XmlReaderは、XMLを先頭から順番に読み取ります。XML全体をメモリ上に保持しないため、大容量ファイルの解析に向いています。ただし、一度通過した場所へ簡単に戻ることはできません。
XDocumentは、XML全体をメモリ上のツリーとして読み込みます。LINQを使って検索や抽出ができるため、コードの可読性が高く、新規開発で扱いやすい方法です。
XmlDocumentもXML全体をツリーとして読み込みます。W3C DOMに近いAPIを備えており、SelectSingleNodeやSelectNodesを使ったXPath検索に適しています。既存の.NET Frameworkコードで多く利用されています。
2. C# XML Parserの選び方
2-1. 結論:用途別のおすすめ
C# XML Parserを選ぶときの基本的な判断基準は次のとおりです。
数百MB以上の大容量XMLを順番に処理するなら
XmlReader読みやすく保守しやすいコードを書きたいなら
XDocumentXPathを多用する既存コードを保守するなら
XmlDocumentXMLとC#オブジェクトを相互変換するなら
XmlSerializer
一般的な新規開発で、XMLのサイズがメモリに十分収まる場合は、まずXDocumentを検討するとよいでしょう。
2-2. 大容量XMLならXmlReader
XmlReaderはXML全体をメモリに展開せず、現在読み取っているノードを中心に処理します。そのため、巨大なログファイルや大量の商品データなどを1件ずつ処理する用途に適しています。
例えば、100万件のitem要素を含むXMLでも、各要素を読み取った直後にデータベースへ登録し、不要になった情報を保持しない実装が可能です。
一方で、XML内の離れた場所にある情報を何度も参照したり、読み取ったノードを自由に編集したりする処理には向いていません。
2-3. 読み書きしやすさ重視ならXDocument
XDocumentはLINQ to XMLに対応しており、要素の検索、絞り込み、並べ替え、変換をC#のLINQ構文で記述できます。
var expensiveBooks = document.Descendants("book").Where(book => (int?)book.Element("price") >= 3000);要素名や属性名をコード上で確認しやすく、追加、変更、削除も簡潔に書けます。XMLのサイズが極端に大きくなければ、可読性と保守性のバランスに優れた選択肢です。
2-4. 既存コードやDOM操作ならXmlDocument
XmlDocumentは、XmlNode、XmlElement、XmlAttributeなどのDOM形式のAPIを提供します。
XmlNode? node = document.SelectSingleNode("/catalog/book/title");古い.NET Framework製システムではXmlDocumentを利用したコードが多く、既存処理を大きく変更せず保守したい場合に適しています。
XPathによる検索を中心に設計されているシステムでも、XmlDocumentは有力な選択肢です。
2-5. パフォーマンス・可読性・保守性の比較表
| 比較項目 | XmlReader | XDocument | XmlDocument |
| 処理方式 | ストリーム型 | インメモリ型 | インメモリ型 |
| メモリ使用量 | 少ない | XMLサイズに応じて増える | XMLサイズに応じて増える |
| 読み取り速度 | 高速になりやすい | 十分高速 | 十分高速 |
| コードの簡潔さ | やや複雑 | 非常に高い | 標準的 |
| XML編集 | 不向き | 得意 | 得意 |
| LINQ | 直接利用しにくい | 標準対応 | 直接利用しにくい |
| XPath | 限定的 | 拡張メソッドで対応 | 標準的に対応 |
| 大容量XML | 非常に適している | 不向きな場合がある | 不向きな場合がある |
| 新規開発 | 大容量処理向け | 第一候補 | XPath中心なら候補 |
| 既存システム保守 | ケースによる | ケースによる | 適している |
3. XmlReaderの使い方
3-1. XmlReaderとは
XmlReaderは、XMLを先頭から末尾へ順番に読み取る前方専用のXMLパーサーです。現在位置のノードについて、要素名、属性、テキスト、ノード種別などを取得できます。
主な特徴は次のとおりです。
XML全体をメモリへ読み込まない
前方向へ読み進める
読み取り専用
大容量XMLに適している
必要な要素だけを抽出できる
利用するには、通常System.Xml名前空間を読み込みます。
using System.Xml;3-2. XmlReaderでXMLファイルを読み込む基本コード
次のコードは、XMLファイル内のすべてのノードを順番に読み取る基本例です。
using System;using System.Xml;var settings = new XmlReaderSettings{DtdProcessing = DtdProcessing.Prohibit,XmlResolver = null};
using XmlReader reader = XmlReader.Create("books.xml", settings);
while (reader.Read()){Console.WriteLine($"NodeType={reader.NodeType}, Name={reader.Name}, Value={reader.Value}");}
Readメソッドを呼び出すたびに、次のノードへ移動します。ファイルの終端まで到達するとfalseを返します。
XmlReaderは破棄が必要なリソースを使用するため、using文またはusing宣言で確実に解放します。
3-3. 要素・属性・テキストを取得する方法
次のXMLから、書籍IDとタイトルを取得します。
<catalog><book id="B001"><title>C#入門</title></book></catalog>using System;using System.Xml;using XmlReader reader = XmlReader.Create("books.xml");
while (reader.Read()){if (reader.NodeType == XmlNodeType.Element &&reader.Name == "book"){string? id = reader.GetAttribute("id");Console.WriteLine($"ID: {id}");}
if (reader.NodeType == XmlNodeType.Element &&reader.Name == "title"){string title = reader.ReadElementContentAsString();Console.WriteLine($"タイトル: {title}");}
}
属性値はGetAttributeで取得できます。指定した属性が存在しない場合はnullが返ります。
要素内のテキストを取得する場合は、ReadElementContentAsStringが便利です。数値として取得する場合は、次のメソッドも利用できます。
int stock = reader.ReadElementContentAsInt();decimal price = reader.ReadElementContentAsDecimal();bool enabled = reader.ReadElementContentAsBoolean();ただし、対象要素が複雑な子要素を含む場合は、単純な値として読み取れないことがあります。
3-4. 大容量XMLを高速に処理する実装例
次のXMLに大量のproduct要素が含まれているとします。
<products><product id="P001"><name>キーボード</name><price>5000</price></product><product id="P002"><name>マウス</name><price>3000</price></product></products>ReadSubtreeを使うと、現在の要素だけを対象とするサブリーダーを作成できます。
using System;using System.Xml;var settings = new XmlReaderSettings{DtdProcessing = DtdProcessing.Prohibit,XmlResolver = null,IgnoreComments = true,IgnoreWhitespace = true};
using XmlReader reader = XmlReader.Create("products.xml", settings);
while (reader.Read()){if (reader.NodeType != XmlNodeType.Element ||reader.Name != "product"){continue;}
string id = reader.GetAttribute("id") ?? string.Empty;string name = string.Empty;decimal price = 0;using XmlReader productReader = reader.ReadSubtree();while (productReader.Read()){if (productReader.NodeType != XmlNodeType.Element){continue;}switch (productReader.Name){case "name":name = productReader.ReadElementContentAsString();break;case "price":price = productReader.ReadElementContentAsDecimal();break;}}Console.WriteLine($"{id}: {name} / {price:N0}円");// 実務では、ここでデータベース登録や集計などを行う
}
この方法では、すべての商品をリストへ保存せず、1件ずつ処理できます。処理済みデータを保持しなければ、商品数が増えてもメモリ使用量を抑えられます。
3-5. XmlReaderを使うメリット・デメリット
XmlReaderのメリットは次のとおりです。
大容量XMLでもメモリ使用量を抑えられる
必要なデータだけを読み取れる
順次処理に適している
不要なコメントや空白を無視できる
非同期読み取りにも対応できる
一方、デメリットもあります。
現在位置を意識して実装する必要がある
一度読み過ぎると前のノードへ戻れない
複雑な検索条件を記述しにくい
XMLの追加、更新、削除には向かない
XDocumentよりコード量が増えやすい
3-6. XmlReaderが向いているケース
XmlReaderが向いているのは、次のようなケースです。
数百MBから数GB規模のXMLを処理する
XML内の要素を先頭から順番に処理できる
必要な情報が限定されている
1件ずつデータベースへ登録する
XML形式のログを集計する
メモリ使用量を最小限に抑えたい
XML全体を自由に参照したり編集したりする必要がなければ、XmlReaderは高いパフォーマンスを期待できます。
4. XDocumentの使い方
4-1. XDocumentとは
XDocumentは、LINQ to XMLを利用してXMLを操作するためのクラスです。XML全体をメモリ上のツリーとして保持し、要素をXElement、属性をXAttributeとして扱います。
using System.Xml.Linq;XDocumentでは、LINQのWhere、Select、OrderByなどを使ってXMLデータを検索・変換できます。新規開発において、扱うXMLがメモリに収まるサイズなら有力な選択肢です。
4-2. XDocumentでXMLを読み込む基本コード
XMLファイルを読み込むにはXDocument.Loadを使用します。
using System;using System.Xml.Linq;XDocument document = XDocument.Load("books.xml");
Console.WriteLine(document.Root?.Name);
XML文字列を解析する場合はXDocument.Parseを使います。
string xml = """<catalog><book id="B001"><title>C#入門</title></book></catalog>""";XDocument document = XDocument.Parse(xml);
ルート要素はRootプロパティから取得できます。XML宣言やコメントを含む文書全体を表すのがXDocumentで、個々の要素を表すのがXElementです。
4-3. LINQ to XMLで要素を検索・抽出する方法
次のXMLから、3,000円以上の書籍を抽出します。
<catalog><book id="B001"><title>C#入門</title><price>2980</price></book><book id="B002"><title>ASP.NET Core実践</title><price>4200</price></book></catalog>using System;using System.Linq;using System.Xml.Linq;XDocument document = XDocument.Load("books.xml");
var books = document.Descendants("book").Where(book => (decimal?)book.Element("price") >= 3000).Select(book => new{Id = (string?)book.Attribute("id"),Title = (string?)book.Element("title"),Price = (decimal?)book.Element("price") ?? 0});
foreach (var book in books){Console.WriteLine($"{book.Id}: {book.Title} / {book.Price:N0}円");}
Descendantsは、子孫要素から指定した名前の要素を検索します。直下の子要素だけを取得する場合はElementsを使用します。
IEnumerable<XElement> directBooks =document.Root?.Elements("book")?? Enumerable.Empty<XElement>();最初の要素だけが必要な場合はElementを使います。
XElement? firstBook = document.Root?.Element("book");4-4. 属性値を取得・変更する方法
属性値はAttributeメソッドで取得できます。
XElement? book = document.Descendants("book").FirstOrDefault();string? id = (string?)book?.Attribute("id");
明示的キャストを使うことで、文字列だけでなく数値や真偽値としても取得できます。
int? stock = (int?)book?.Attribute("stock");bool? published = (bool?)book?.Attribute("published");属性を追加または変更する場合はSetAttributeValueを使用します。
book?.SetAttributeValue("stock", 20);book?.SetAttributeValue("updatedAt", DateTime.UtcNow);属性を削除するには、値としてnullを指定するか、Removeを呼び出します。
book?.SetAttributeValue("stock", null);// またはbook?.Attribute("stock")?.Remove();
要素の値を変更する場合はSetElementValueが便利です。
book?.SetElementValue("price", 3500);4-5. XMLを新規作成・保存する方法
XDocumentとXElementを組み合わせると、XMLを宣言的に作成できます。
using System.Xml.Linq;var document = new XDocument(new XDeclaration("1.0", "utf-8", null),new XElement("catalog",new XElement("book",new XAttribute("id", "B001"),new XElement("title", "C#入門"),new XElement("price", 2980)),new XElement("book",new XAttribute("id", "B002"),new XElement("title", "ASP.NET Core実践"),new XElement("price", 4200))));
document.Save("books.xml");
既存の文書へ要素を追加する場合はAddを使用します。
document.Root?.Add(new XElement("book",new XAttribute("id", "B003"),new XElement("title", "LINQ実践"),new XElement("price", 3200)));document.Save("books.xml");
インデントを付けずに保存したい場合は、SaveOptions.DisableFormattingを指定できます。
document.Save("books.xml", SaveOptions.DisableFormatting);4-6. XDocumentを使うメリット・デメリット
XDocumentのメリットは次のとおりです。
LINQで検索や変換を記述できる
コードが簡潔で読みやすい
要素や属性の追加、更新、削除が容易
XMLを宣言的に作成できる
名前空間付きXMLも比較的扱いやすい
C#の匿名型やオブジェクトへ変換しやすい
デメリットは次のとおりです。
XML全体をメモリに保持する
巨大なXMLではメモリ不足になる可能性がある
DOMベースの既存コードとAPIが異なる
複雑なXPath資産をそのまま移行しにくい場合がある
4-7. XDocumentが向いているケース
XDocumentは次の用途に向いています。
数KBから数十MB程度のXMLを扱う
LINQを使ってデータを抽出したい
XMLを追加、変更、削除したい
新規開発で保守しやすいコードを書きたい
XMLからC#オブジェクトや集計結果を作りたい
名前空間付きXMLを扱いたい
特別な理由がなく、XML全体がメモリに収まるなら、最初に検討しやすいC# XML Parserです。
5. XmlDocumentの使い方
5-1. XmlDocumentとは
XmlDocumentは、XMLをDOM形式で扱うクラスです。XML全体をメモリに読み込み、各要素をXmlNodeやXmlElementとして操作します。
using System.Xml;主な特徴は、XPathを利用した検索と、DOM形式のノード操作です。長期間運用されている.NET Frameworkシステムでは、XmlDocumentを使った実装が多く見られます。
5-2. XmlDocumentでXMLを読み込む基本コード
using System;using System.Xml;var document = new XmlDocument{XmlResolver = null};
document.Load("books.xml");
Console.WriteLine(document.DocumentElement?.Name);
XML文字列を解析する場合はLoadXmlを使用します。
string xml = """<catalog><book id="B001"><title>C#入門</title></book></catalog>""";var document = new XmlDocument{XmlResolver = null};
document.LoadXml(xml);
ルート要素はDocumentElementから取得できます。
5-3. SelectSingleNode・SelectNodesで要素を取得する方法
最初に一致したノードを取得するにはSelectSingleNodeを使います。
XmlNode? titleNode =document.SelectSingleNode("/catalog/book/title");Console.WriteLine(titleNode?.InnerText);
複数のノードを取得するにはSelectNodesを使用します。
XmlNodeList? bookNodes =document.SelectNodes("/catalog/book");if (bookNodes is not null){foreach (XmlNode bookNode in bookNodes){string? id = bookNode.Attributes?["id"]?.Value;string? title = bookNode["title"]?.InnerText;string? price = bookNode["price"]?.InnerText;
Console.WriteLine($"{id}: {title} / {price}円");}
}
SelectSingleNodeとSelectNodesの引数にはXPathを指定します。
5-4. XPathを使ったXML検索
XPathを利用すると、階層、属性、条件を指定してノードを検索できます。
IDがB002の書籍を取得する例です。
XmlNode? book = document.SelectSingleNode("/catalog/book[@id='B002']");価格が3,000より大きい書籍を取得する例です。
XmlNodeList? expensiveBooks = document.SelectNodes("/catalog/book[number(price) > 3000]");すべての階層からtitle要素を検索する場合は、次のように記述します。
XmlNodeList? titles = document.SelectNodes("//title");XPathは強力ですが、外部入力を文字列連結してXPathへ埋め込むと、意図しない検索条件になる可能性があります。検索値を動的に組み立てる場合は、入力値の検証が必要です。
5-5. ノードの追加・更新・削除
新しい書籍ノードを追加する例です。
XmlElement newBook = document.CreateElement("book");newBook.SetAttribute("id", "B003");XmlElement title = document.CreateElement("title");title.InnerText = "LINQ実践";
XmlElement price = document.CreateElement("price");price.InnerText = "3200";
newBook.AppendChild(title);newBook.AppendChild(price);
document.DocumentElement?.AppendChild(newBook);
document.Save("books.xml");
要素を更新する場合はInnerTextを変更します。
XmlNode? priceNode = document.SelectSingleNode("/catalog/book[@id='B001']/price");if (priceNode is not null){priceNode.InnerText = "3500";}
ノードを削除する場合は、親ノードのRemoveChildを使用します。
XmlNode? targetBook = document.SelectSingleNode("/catalog/book[@id='B002']");if (targetBook?.ParentNode is not null){targetBook.ParentNode.RemoveChild(targetBook);}
変更後はSaveで保存します。
document.Save("books.xml");5-6. XmlDocumentを使うメリット・デメリット
XmlDocumentのメリットは次のとおりです。
XPathによる検索を記述しやすい
DOM形式でノードを操作できる
長年利用されており、既存コードが多い
ノードの追加、更新、削除に対応できる
.NET Frameworkの古いプロジェクトでも利用しやすい
デメリットは次のとおりです。
XML全体をメモリに保持する
XDocumentよりコードが長くなりやすいLINQを直接利用しにくい
要素作成時に
CreateElementなどの手順が増える大容量XMLの処理には向かない
5-7. XmlDocumentが向いているケース
XmlDocumentは、次のような場面に適しています。
既存コードが
XmlDocumentを使用しているXPath式が多数蓄積されている
DOM形式のAPIに慣れたチームで開発する
既存処理への影響を抑えて改修したい
古い.NET Framework環境との互換性が重要
新規開発ではXDocumentのほうが簡潔になることが多いものの、XmlDocumentが使えないわけではありません。既存資産やチームの技術構成を考慮して選択します。
6. XmlReader・XDocument・XmlDocumentの違いを比較
6-1. 処理方式の違い
XmlReaderはストリーム型です。XMLを先頭から順番に読み取り、現在位置のノードを処理します。
XDocumentとXmlDocumentはツリー型です。XML全体を読み込んでメモリ上に階層構造を構築します。そのため、任意の要素へ移動したり、同じデータを何度も参照したりできます。
処理方式の違いは、メモリ使用量とコードの構造に大きく影響します。
6-2. メモリ使用量の違い
通常、最もメモリ使用量を抑えやすいのはXmlReaderです。必要なノードを順次処理できるため、XML全体のサイズに比例してメモリが増えることを避けられます。
XDocumentとXmlDocumentは、XMLデータに加えてツリーを管理するオブジェクトも作成します。そのため、メモリ使用量は元のXMLファイルサイズより大きくなる場合があります。
ただし、数KBから数MB程度のXMLであれば、メモリ差よりもコードの読みやすさを優先したほうがよいこともあります。
6-3. コードの書きやすさの違い
コードの簡潔さでは、一般的にXDocumentが優れています。
var titles = document.Descendants("book").Select(book => (string?)book.Element("title"));XmlDocumentではXPathを使うことで検索を簡潔に書けますが、ノードから値を取り出したり、新しい要素を作成したりするコードは長くなりやすい傾向があります。
XmlReaderは読み取り位置やノード種別を管理する必要があるため、3種類の中では実装が複雑になりやすい方法です。
6-4. XML編集のしやすさの違い
XML編集にはXDocumentまたはXmlDocumentが適しています。
XDocumentでは、要素を直接追加できます。
document.Root?.Add(new XElement("status", "active"));XmlDocumentでは、文書に所属するノードを作成して追加します。
XmlElement status = document.CreateElement("status");status.InnerText = "active";document.DocumentElement?.AppendChild(status);XmlReaderは読み取り専用なので、読み込み中のXMLを直接変更できません。編集結果を出力したい場合は、XmlWriterと組み合わせて新しいXMLを生成します。
6-5. XPath対応の違い
XmlDocumentはSelectSingleNodeとSelectNodesによってXPathを直接利用できます。
XDocumentでも、System.Xml.XPath名前空間の拡張メソッドを利用すればXPath検索が可能です。
using System.Xml.Linq;using System.Xml.XPath;XDocument document = XDocument.Load("books.xml");
XElement? book = document.XPathSelectElement("/catalog/book[@id='B001']");
ただし、XDocumentではLINQ to XMLによる検索が中心です。LINQで十分に表現できる場合は、無理にXPathへ統一する必要はありません。
XmlReaderはツリーを構築しないため、一般的なXPath検索には向いていません。
6-6. 実務で迷ったときの判断基準
実務で迷った場合は、次の順番で判断すると選びやすくなります。
XMLが大きく、全体をメモリに読み込めないなら
XmlReader既存コードが
XmlDocumentとXPathに依存しているならXmlDocumentそれ以外の一般的な新規開発なら
XDocumentXMLをクラスへ変換することが目的なら
XmlSerializer
性能だけで決めるのではなく、コードの保守性、XMLサイズ、編集の有無、既存資産を含めて判断することが重要です。
7. C#でXMLを解析する実践サンプル
7-1. サンプルXMLの構造
ここでは、3種類のC# XML Parserで同じXMLを解析します。
<?xml version="1.0" encoding="utf-8"?><catalog><book id="B001" category="programming"><title>C#入門</title><author>山田太郎</author><price>2980</price></book><book id="B002" category="web"><title>ASP.NET Core実践</title><author>佐藤花子</author><price>4200</price></book><book id="B003" category="database"><title>SQL設計ガイド</title><author>鈴木一郎</author><price>3600</price></book></catalog>取得する情報は、書籍ID、カテゴリ、タイトル、著者、価格です。
7-2. XmlReaderでデータを抽出する例
using System;using System.Collections.Generic;using System.Xml;public sealed record Book(string Id,string Category,string Title,string Author,decimal Price);
var books = new List<Book>();
using XmlReader reader = XmlReader.Create("books.xml");
while (reader.Read()){if (reader.NodeType != XmlNodeType.Element ||reader.Name != "book"){continue;}
string id = reader.GetAttribute("id") ?? string.Empty;string category =reader.GetAttribute("category") ?? string.Empty;string title = string.Empty;string author = string.Empty;decimal price = 0;using XmlReader bookReader = reader.ReadSubtree();while (bookReader.Read()){if (bookReader.NodeType != XmlNodeType.Element){continue;}switch (bookReader.Name){case "title":title = bookReader.ReadElementContentAsString();break;case "author":author = bookReader.ReadElementContentAsString();break;case "price":price = bookReader.ReadElementContentAsDecimal();break;}}books.Add(new Book(id, category, title, author, price));
}
foreach (Book book in books){Console.WriteLine(book);}
大容量処理では、List<Book>へ追加せず、その場でデータベース登録や集計を行うとメモリ使用量をさらに抑えられます。
7-3. XDocumentでデータを抽出する例
using System;using System.Linq;using System.Xml.Linq;public sealed record Book(string Id,string Category,string Title,string Author,decimal Price);
XDocument document = XDocument.Load("books.xml");
var books = document.Descendants("book").Select(element => new Book(Id: (string?)element.Attribute("id") ?? string.Empty,Category:(string?)element.Attribute("category") ?? string.Empty,Title:(string?)element.Element("title") ?? string.Empty,Author:(string?)element.Element("author") ?? string.Empty,Price:(decimal?)element.Element("price") ?? 0)).ToList();
foreach (Book book in books){Console.WriteLine(book);}
3種類の中で最も短く、抽出内容を把握しやすいコードです。
7-4. XmlDocumentでデータを抽出する例
using System;using System.Collections.Generic;using System.Globalization;using System.Xml;public sealed record Book(string Id,string Category,string Title,string Author,decimal Price);
var document = new XmlDocument{XmlResolver = null};
document.Load("books.xml");
var books = new List<Book>();XmlNodeList? nodes = document.SelectNodes("/catalog/book");
if (nodes is not null){foreach (XmlNode node in nodes){string id = node.Attributes?["id"]?.Value?? string.Empty;
string category =node.Attributes?<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">["category"]</span>?.Value?? string.Empty;string title = node<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">["title"]</span>?.InnerText?? string.Empty;string author = node<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">["author"]</span>?.InnerText?? string.Empty;decimal.TryParse(node<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">["price"]</span>?.InnerText,NumberStyles.Number,CultureInfo.InvariantCulture,out decimal price);books.Add(new Book(id, category, title, author, price));}
}
foreach (Book book in books){Console.WriteLine(book);}
XPathで対象ノードを指定し、その後に属性や子要素を取得しています。
7-5. 3つの実装コードの比較
同じXMLを解析しても、実装方法には明確な違いがあります。
XmlReaderはコード量が多いものの、1件ずつ処理できるため大容量データに適しています。
XDocumentはLINQでXMLからオブジェクトへの変換をまとめて記述でき、可読性に優れています。
XmlDocumentはXPathによる対象ノードの指定が分かりやすく、既存のXPath式を活用できます。
単純な実行速度だけでなく、XMLサイズと保守性を考慮して選択してください。
8. XML解析でよく使う処理
8-1. XML文字列をパースする方法
XDocumentでXML文字列をパースする場合はParseを使用します。
string xml = "<root><message>Hello</message></root>";XDocument document = XDocument.Parse(xml);
string? message =(string?)document.Root?.Element("message");
XmlDocumentではLoadXmlを使います。
var document = new XmlDocument{XmlResolver = null};document.LoadXml(xml);
string? message =document.SelectSingleNode("/root/message")?.InnerText;
XmlReaderではStringReaderと組み合わせます。
using System.IO;using System.Xml;using var stringReader = new StringReader(xml);using XmlReader reader = XmlReader.Create(stringReader);
while (reader.Read()){if (reader.NodeType == XmlNodeType.Element &&reader.Name == "message"){Console.WriteLine(reader.ReadElementContentAsString());}}
8-2. XMLファイルを読み込む方法
XDocumentの場合は次のとおりです。
XDocument document = XDocument.Load("data.xml");XmlDocumentの場合は次のとおりです。
var document = new XmlDocument{XmlResolver = null};document.Load("data.xml");
XmlReaderの場合は次のとおりです。
using XmlReader reader = XmlReader.Create("data.xml");while (reader.Read()){// ノードを処理する}
外部から受け取ったXMLを読み込む場合は、後述するDTDや外部エンティティへの対策を含む設定を明示するのが安全です。
8-3. 属性値を取得する方法
XmlReaderではGetAttributeを使用します。
string? id = reader.GetAttribute("id");XDocumentではAttributeを使用します。
string? id = (string?)element.Attribute("id");XmlDocumentではAttributesまたはXmlElement.GetAttributeを使用します。
string? id = node.Attributes?["id"]?.Value;if (node is XmlElement element){string id = element.GetAttribute("id");}属性が存在しない可能性を考慮し、nullや空文字列を適切に処理します。
8-4. 複数ノードをループ処理する方法
XDocumentでは、LINQの結果をforeachで処理します。
foreach (XElement book in document.Descendants("book")){Console.WriteLine((string?)book.Element("title"));}XmlDocumentではXmlNodeListを処理します。
XmlNodeList? nodes =document.SelectNodes("/catalog/book");if (nodes is not null){foreach (XmlNode node in nodes){Console.WriteLine(node["title"]?.InnerText);}}
XmlReaderでは、Readを繰り返しながら対象要素を判定します。
while (reader.Read()){if (reader.NodeType == XmlNodeType.Element &&reader.Name == "title"){Console.WriteLine(reader.ReadElementContentAsString());}}8-5. 名前空間付きXMLを扱う方法
XML名前空間が設定されている場合、見た目の要素名だけでは取得できないことがあります。
<catalog xmlns="https://example.com/books"><book id="B001"><title>C#入門</title></book></catalog>XDocumentではXNamespaceを使います。
XDocument document = XDocument.Load("books.xml");XNamespace ns = "https://example.com/books";
var books = document.Descendants(ns + "book").Select(book => new{Id = (string?)book.Attribute("id"),Title = (string?)book.Element(ns + "title")});
デフォルト名前空間が設定されていても、属性に接頭辞がなければ、通常その属性はデフォルト名前空間に属しません。そのため、上のid属性は単純に"id"で取得します。
XmlDocumentではXmlNamespaceManagerを使用します。
var manager =new XmlNamespaceManager(document.NameTable);manager.AddNamespace("b","https://example.com/books");
XmlNodeList? books = document.SelectNodes("/b:catalog/b:book",manager);
XPath上の接頭辞bは、元のXMLで使用されている接頭辞と同じである必要はありません。名前空間URIとの対応が正しければ検索できます。
XmlReaderでは、NameではなくLocalNameとNamespaceURIを確認すると安全です。
const string namespaceUri ="https://example.com/books";if (reader.NodeType == XmlNodeType.Element &&reader.LocalName == "book" &&reader.NamespaceURI == namespaceUri){// book要素を処理する}
8-6. XMLをJSONやオブジェクトに変換する方法
XMLをC#オブジェクトへ変換する場合はXmlSerializerが便利です。
次のXMLを対象とします。
<Book><Id>B001</Id><Title>C#入門</Title><Price>2980</Price></Book>対応するクラスを作成します。
public sealed class Book{public string Id { get; set; } = string.Empty;public string Title { get; set; } = string.Empty;public decimal Price { get; set; }
}
XMLからオブジェクトへ変換します。
using System.IO;using System.Xml.Serialization;var serializer = new XmlSerializer(typeof(Book));
using var stream = File.OpenRead("book.xml");
var book = serializer.Deserialize(stream) as Book;
Console.WriteLine(book?.Title);
オブジェクトからXMLへ変換する場合はSerializeを使用します。
var book = new Book{Id = "B001",Title = "C#入門",Price = 2980};var serializer = new XmlSerializer(typeof(Book));
using var stream = File.Create("book.xml");serializer.Serialize(stream, book);
XMLをJSONへ変換する場合、標準のSystem.Text.JsonはXMLを直接入力として扱いません。一般的には次の手順で変換します。
XMLを
XmlSerializerなどでC#オブジェクトへ変換するそのオブジェクトを
JsonSerializerでJSONへ変換する
using System.Text.Json;string json = JsonSerializer.Serialize(book,new JsonSerializerOptions{WriteIndented = true});
XMLには属性、名前空間、混合コンテンツなどJSONと異なる構造があるため、単純な変換では情報を完全に維持できない場合があります。
9. C# XML Parser利用時の注意点
9-1. 文字コードとエンコーディングの扱い
XMLファイルの先頭には、エンコーディングを示すXML宣言を記述できます。
<?xml version="1.0" encoding="utf-8"?>ファイルやストリームからXMLを読み込む場合、XMLパーサーはXML宣言やバイトオーダーマークなどを基にエンコーディングを判定します。
文字化けを避けるには、次の点を確認してください。
XML宣言のエンコーディングと実際のファイル形式を一致させる
UTF-8として保存したファイルをShift_JISとして読み込まない
バイト列を文字列へ変換するときに正しい
Encodingを使うXML保存時のエンコーディングを明示する
文字列として受け取ったXMLは、すでに.NETの文字列としてデコード済みです。XML宣言のencodingを書き換えても、壊れた文字列が元に戻るわけではありません。
エンコーディングを指定して保存する例です。
using System.Text;using System.Xml;var settings = new XmlWriterSettings{Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false),Indent = true};
using XmlWriter writer =XmlWriter.Create("output.xml", settings);
document.Save(writer);
9-2. XML名前空間で要素が取得できない原因
要素名が正しいのに取得できない場合、最もよくある原因の一つがXML名前空間です。
次のXMLのbookは、単なるbookではなく、名前空間URIとローカル名を組み合わせた要素です。
<catalog xmlns="https://example.com/books"><book /></catalog>そのため、次のコードでは取得できません。
document.Descendants("book");XNamespaceを含めて指定します。
XNamespace ns = "https://example.com/books";IEnumerable<XElement> books =document.Descendants(ns + "book");
名前空間付きXMLを扱うときは、接頭辞の文字列ではなく名前空間URIを確認することが重要です。
9-3. 不正なXMLを読み込んだときの例外処理
閉じタグがない、属性の引用符が閉じていない、ルート要素が複数あるなど、XMLの形式が不正な場合はXmlExceptionが発生します。
using System;using System.Xml;using System.Xml.Linq;try{XDocument document = XDocument.Load("books.xml");}catch (XmlException ex){Console.WriteLine($"XML形式エラー: {ex.Message}");
Console.WriteLine($"行: {ex.LineNumber}, 位置: {ex.LinePosition}");
}catch (IOException ex){Console.WriteLine($"ファイル読み込みエラー: {ex.Message}");}catch (UnauthorizedAccessException ex){Console.WriteLine($"アクセス権限エラー: {ex.Message}");}
XML形式の問題と、ファイルが存在しない、アクセス権限がないといった入出力エラーは分けて処理すると、原因を特定しやすくなります。
例外を単に握りつぶすのではなく、ファイル名、行番号、エラー位置などをログへ記録することが重要です。
9-4. XXE攻撃などXML解析時のセキュリティ対策
外部から受け取ったXMLを解析するときは、XXEと呼ばれる外部エンティティを悪用した攻撃に注意が必要です。
安全性を明確にするため、XmlReaderSettingsでDTDを禁止し、外部リソースの解決を無効にします。
var settings = new XmlReaderSettings{DtdProcessing = DtdProcessing.Prohibit,XmlResolver = null};using XmlReader reader =XmlReader.Create("input.xml", settings);
XDocumentを安全な設定で読み込みたい場合は、設定済みのXmlReaderを渡します。
var settings = new XmlReaderSettings{DtdProcessing = DtdProcessing.Prohibit,XmlResolver = null};using XmlReader reader =XmlReader.Create("input.xml", settings);
XDocument document = XDocument.Load(reader);
XmlDocumentでは、外部リソースを解決しないようXmlResolverをnullに設定します。
var document = new XmlDocument{XmlResolver = null};document.Load("input.xml");
加えて、外部入力を扱う場合は次の対策も有効です。
受信できるXMLサイズを制限する
要素数や階層の深さを検証する
処理時間に上限を設ける
信頼できないDTDを許可しない
エラーメッセージに内部ファイルパスを露出させない
DTDが業務上必要な場合は、無条件に外部参照を許可するのではなく、信頼できるリソースだけに限定する設計が必要です。
9-5. usingによるリソース解放
XmlReader、Stream、TextReader、XmlWriterなどは、使用後に破棄する必要があります。
using XmlReader reader = XmlReader.Create("data.xml");while (reader.Read()){// 読み取り処理}
複数のリソースを使用する場合も、using宣言を利用できます。
using FileStream stream = File.OpenRead("data.xml");using XmlReader reader = XmlReader.Create(stream);while (reader.Read()){// 読み取り処理}
XDocumentとXmlDocument自体は、通常usingで囲む必要はありません。ただし、それらへ渡すストリームやリーダーは適切に破棄します。
10. よくあるエラーと解決策
10-1. ルート要素が見つからない
空のファイルや空文字列をXMLとして解析すると、ルート要素がないためXmlExceptionが発生します。
string xml = "";XDocument document = XDocument.Parse(xml);
対策として、解析前に入力が空でないか確認します。
if (string.IsNullOrWhiteSpace(xml)){throw new ArgumentException("XMLが空です。",nameof(xml));}XDocument document = XDocument.Parse(xml);
ルート要素名が想定どおりかも確認します。
if (document.Root?.Name.LocalName != "catalog"){throw new InvalidDataException("ルート要素がcatalogではありません。");}10-2. 要素名は合っているのに取得できない
要素名が合っているのに取得できない場合は、次の点を確認します。
デフォルト名前空間が設定されていないか
大文字と小文字が一致しているか
対象要素が直下の子か子孫か
読み込んでいるXMLファイルが正しいか
XmlReaderがすでに対象要素を通過していないか
XMLの要素名は大文字と小文字を区別します。Bookとbookは別の要素です。
また、Elementsは直下の子だけを検索します。
document.Root?.Elements("book");すべての子孫から検索する場合はDescendantsを使います。
document.Descendants("book");10-3. XPathでノードが取得できない
XPathでノードを取得できない場合は、次の原因が考えられます。
XPathがルート要素からの構造と一致していない
名前空間マネージャーを設定していない
属性指定の記述が間違っている
相対パスと絶対パスを混同している
XPath上の要素名で大文字と小文字が違う
名前空間付きXMLでは、XmlNamespaceManagerを指定します。
var manager =new XmlNamespaceManager(document.NameTable);manager.AddNamespace("b","https://example.com/books");
XmlNode? node = document.SelectSingleNode("/b:catalog/b:book[@id='B001']",manager);
デバッグ時には、まず広い条件で取得できるか確認し、徐々に条件を追加すると原因を特定しやすくなります。
document.SelectNodes("//*");10-4. 日本語が文字化けする
日本語の文字化けは、XMLパーサーではなく、ファイルの保存形式と読み込み時のエンコーディングが一致していないことが原因になりやすい問題です。
例えば、Shift_JISのバイト列をUTF-8として文字列へ変換すると、その後にXMLパーサーへ渡しても修復できません。
byte[] bytes = File.ReadAllBytes("data.xml");string xml = Encoding.GetEncoding("shift_jis").GetString(bytes);
ただし、可能であればバイト列を手動で文字列へ変換せず、XmlReaderやXDocument.Loadへストリームを渡し、XML宣言に基づいて解析させるほうが安全です。
using FileStream stream = File.OpenRead("data.xml");XDocument document = XDocument.Load(stream);保存時にもXML宣言と実際のエンコーディングを一致させます。
10-5. 大きなXMLでメモリ不足になる
大きなXMLをXDocument.LoadやXmlDocument.Loadで読み込むと、XML全体がメモリ上に展開されます。ファイルサイズが大きい場合、OutOfMemoryExceptionや著しい処理遅延につながる可能性があります。
解決策はXmlReaderで1件ずつ処理することです。
using XmlReader reader =XmlReader.Create("large-data.xml");while (reader.Read()){if (reader.NodeType == XmlNodeType.Element &&reader.Name == "item"){using XmlReader itemReader = reader.ReadSubtree();
// item単位で処理し、処理後は保持しない}
}
どうしてもLINQ to XMLを使いたい場合は、XmlReaderで対象要素まで移動し、その要素だけをXElement.Loadで読み込む方法があります。
using XmlReader reader =XmlReader.Create("large-data.xml");while (reader.Read()){if (reader.NodeType == XmlNodeType.Element &&reader.Name == "item"){XElement item =(XElement)XNode.ReadFrom(reader);
// item要素だけをLINQ to XMLで処理する}
}
この方法なら、ストリーム処理とLINQ to XMLの扱いやすさを組み合わせられます。
11. C# XML Parserに関するよくある質問
11-1. C#でXMLを読み込むにはどれを使うべき?
一般的な新規開発で、XML全体がメモリに収まる場合はXDocumentがおすすめです。LINQを使って検索や変換を記述でき、コードが読みやすくなります。
巨大なXMLを順番に処理する場合はXmlReaderを選びます。既存コードがDOMやXPathを中心に実装されている場合は、XmlDocumentを継続して使用する選択も合理的です。
11-2. XmlReaderとXDocumentはどちらが速い?
単純な順次読み取りでは、XML全体をツリーへ展開しないXmlReaderのほうが高速かつ省メモリになりやすい傾向があります。
ただし、実際の速度は次の条件で変わります。
XMLのサイズ
検索回数
抽出するデータ量
オブジェクトへの変換処理
データベースやネットワークへの入出力
同じXMLを何度参照するか
小さなXMLでは、両者の速度差よりコードの保守性が重要です。XML全体を何度も検索する場合は、一度ツリー化するXDocumentのほうが実装しやすく、結果として効率的になることもあります。
11-3. XmlDocumentは古いので使わない方がいい?
XmlDocumentは古くからあるAPIですが、古いという理由だけで使用を避ける必要はありません。
既存システムがXmlDocumentに依存している場合や、XPathを多用する場合には、現在も実用的です。無理にXDocumentへ書き換えると、移行コストや不具合のリスクが増える可能性があります。
新規開発ではXDocumentのほうが簡潔になるケースが多いため、特別な理由がなければ両者を比較したうえで選択するとよいでしょう。
11-4. XMLを編集するならどのクラスが便利?
読みやすさとコードの簡潔さを重視するならXDocumentが便利です。
book.SetElementValue("price", 3500);book.SetAttributeValue("updated", true);DOM形式のノード操作や既存のXPath資産を重視するならXmlDocumentが適しています。
XmlReaderは読み取り専用なので、既存XMLの任意のノードを直接編集する用途には向いていません。大容量XMLを変換しながら出力する場合は、XmlReaderとXmlWriterを組み合わせます。
11-5. JSONとXMLはどちらを使うべき?
JSONとXMLのどちらが適しているかは用途によって異なります。
JSONは構造が比較的簡潔で、Web APIやJavaScriptとの連携に向いています。データ量を抑えやすく、多くのモダンなアプリケーションで利用されています。
XMLは次のような用途に向いています。
属性と要素を使い分けたい
名前空間が必要
XML Schemaによる厳密な検証が必要
文書形式のデータを表現したい
SOAPなど既存のXML仕様に対応する
長期間運用されている業界標準を利用する
新しいWeb APIを自由に設計できる場合はJSONが選ばれることが多い一方、既存仕様や複雑な文書構造を扱う場合はXMLが適しています。
まとめ
C#でXMLを解析するときは、目的に合ったXMLパーサーを選ぶことが重要です。
XmlReaderは、XMLを先頭から順番に読み取るストリーム型のパーサーです。メモリ使用量が少なく、大容量XMLの高速な順次処理に適しています。ただし、読み取り位置を意識した実装が必要で、XMLの編集には向きません。
XDocumentは、LINQ to XMLを利用できるインメモリ型のパーサーです。検索、抽出、追加、更新、削除を簡潔に記述でき、一般的な新規開発で使いやすい選択肢です。
XmlDocumentは、DOM形式のAPIとXPath検索を提供します。既存の.NET Frameworkシステムや、XPathを中心に構築された処理の保守に適しています。
選び方を整理すると、次のようになります。
大容量XMLを省メモリで処理するなら
XmlReader読みやすさと編集のしやすさを重視するなら
XDocumentXPathや既存のDOMコードを活用するなら
XmlDocumentXMLとC#オブジェクトを変換するなら
XmlSerializer
また、実務では名前空間、文字コード、不正なXML、リソース解放、XXEなどのセキュリティ対策も欠かせません。特に外部から受け取ったXMLは、DTDと外部リソースの解決を明示的に制限し、安全な設定で解析することが大切です。
XMLサイズ、編集の有無、検索方法、既存コードとの互換性を確認し、用途に最も適したC# XML Parserを選択してください。

