C#でXMLをParseする方法|XDocument・XmlDocument・XmlReaderの違いと読み取りサンプルを初心者向けに解説
はじめに
C#でXMLをParse(解析)する方法には、主にXDocument、XmlDocument、XmlReaderの3種類があります。
どの方法でもXMLから要素や属性を取得できますが、操作性、メモリ使用量、XPathへの対応などに違いがあります。そのため、XMLのサイズや処理内容に応じた使い分けが重要です。
一般的なXMLを簡単に読み取る場合はXDocument、XPathを利用したい場合はXmlDocument、大容量XMLを少ないメモリで処理したい場合はXmlReaderが適しています。
この記事では、C#でXMLをParseする基本から、それぞれのクラスを使った読み取りサンプル、よくあるエラー、安全に処理するための注意点まで初心者向けに解説します。
1. C#でXMLをParseする基本
1-1. XMLのParseとは何か
XMLのParseとは、XML形式の文字列やファイルをプログラムで解析し、要素、属性、テキストなどを取り出せる状態にすることです。
たとえば、次のXMLがあるとします。
<book id="1"><title>C#入門</title><price>2800</price></book>このXMLをParseすると、C#のプログラムから次のような情報を取得できます。
book要素のid属性title要素の値price要素の値
XMLは単なる文字列としても扱えますが、文字列検索だけで要素を取り出すと、階層構造や属性、名前空間を正しく処理できません。C#に用意されているXML用クラスを利用することで、安全かつ効率的に解析できます。
1-2. C#でXMLを読み取る主な方法
C#でXMLを読み取る代表的な方法は、次の3つです。
| クラス | 特徴 | 主な用途 |
|---|---|---|
XDocument | LINQを使って直感的に操作できる | 一般的なXMLの読み取り・編集 |
XmlDocument | DOM形式でXML全体を保持し、XPathを利用できる | XPathを使った検索、既存コードの保守 |
XmlReader | XMLを先頭から順番に読み取る | 大容量XMLの高速・省メモリ処理 |
XDocumentとXmlDocumentは、XML全体をメモリ上に読み込んでから操作します。一方、XmlReaderはXMLを順番に読み進めるため、ファイル全体を保持する必要がありません。
1-3. この記事で扱うXDocument・XmlDocument・XmlReaderの違い
3つのクラスには、次のような違いがあります。
XDocumentはLINQ to XMLを利用でき、短く読みやすいコードを書ける点が特徴です。現在のC#で一般的なXMLを扱う場合は、最初の候補になります。
XmlDocumentは従来から利用されているDOM形式のAPIです。XPathによる柔軟な検索に対応しており、既存システムでも多く使われています。
XmlReaderは、XMLをストリームとして一方向に読み取るAPIです。ランダムアクセスや複雑な編集には向きませんが、大容量ファイルを少ないメモリで処理できます。
2. C#でXMLをParseする前に準備するサンプルXML
2-1. 読み取りに使うXMLデータの例
この記事では、次のXMLを使用します。
<?xml version="1.0" encoding="utf-8"?><books><book id="1" category="programming"><title>C#入門</title><author>山田太郎</author><price>2800</price></book><book id="2" category="database"><title>SQL入門</title><author>鈴木花子</author><price>2500</price></book></books>ルート要素はbooksです。その下に複数のbook要素があり、各書籍にはid属性、category属性、title、author、price要素が含まれています。
2-2. ファイルからXMLを読み込む場合
XMLファイルから読み込む場合は、サンプルXMLをbooks.xmlという名前で保存します。
実行ファイルと同じディレクトリに配置した場合は、次のように相対パスを指定できます。
string filePath = "books.xml";Visual Studioのプロジェクトへ追加したXMLファイルを出力先にコピーするには、ファイルのプロパティで次のように設定します。
ビルド アクション: コンテンツ出力ディレクトリにコピー: 新しい場合はコピーする実行環境によるパスの違いを避けたい場合は、AppContext.BaseDirectoryからパスを組み立てる方法もあります。
string filePath = Path.Combine(AppContext.BaseDirectory,"books.xml");2-3. 文字列からXMLを読み込む場合
APIのレスポンスやデータベースから取得したXMLは、文字列としてParseすることがあります。
string xml = """<books><book id="1" category="programming"><title>C#入門</title><author>山田太郎</author><price>2800</price></book><book id="2" category="database"><title>SQL入門</title><author>鈴木花子</author><price>2500</price></book></books>""";上記はC#のRaw String Literalを使った書き方です。古いバージョンのC#では、逐語的文字列リテラルを使用できます。
string xml = @"<books><book id=""1""><title>C#入門</title></book></books>";3. XDocumentでXMLをParseする方法
3-1. XDocumentとは
XDocumentは、LINQ to XMLを利用してXMLを操作するためのクラスです。名前空間はSystem.Xml.Linqです。
using System.Xml.Linq;XMLの要素はXElement、属性はXAttributeとして扱います。LINQのWhere、Select、FirstOrDefaultなどを利用できるため、C#らしい簡潔なコードを書けます。
3-2. XDocument.LoadでXMLファイルを読み込む
XMLファイルを読み込む場合は、XDocument.Loadを使用します。
using System;using System.Xml.Linq;class Program{static void Main(){XDocument document = XDocument.Load("books.xml");
Console.WriteLine(document);}
}
ルート要素を取得する場合は、Rootプロパティを使用します。
XDocument document = XDocument.Load("books.xml");XElement? root = document.Root;
if (root is not null){Console.WriteLine(root.Name);}
実行結果は次のようになります。
books3-3. XDocument.ParseでXML文字列を読み込む
XML文字列を読み込む場合は、XDocument.Parseを使用します。
using System;using System.Xml.Linq;class Program{static void Main(){string xml = """<book id="1"><title>C#入門</title><price>2800</price></book>""";
XDocument document = XDocument.Parse(xml);Console.WriteLine(document.Root?.Element("title")?.Value);}
}
実行結果は次のとおりです。
C#入門XDocument.Parseに渡す文字列は、ルート要素を1つだけ持つ正しいXMLでなければなりません。
3-4. LINQ to XMLで要素や属性を取得する
複数のbook要素を取得するには、ElementsまたはDescendantsを使用します。
XDocument document = XDocument.Load("books.xml");IEnumerable<XElement> books =document.Root?.Elements("book")?? Enumerable.Empty<XElement>();
foreach (XElement book in books){string id = (string?)book.Attribute("id") ?? "";string category = (string?)book.Attribute("category") ?? "";string title = (string?)book.Element("title") ?? "";string author = (string?)book.Element("author") ?? "";int? price = (int?)book.Element("price");
Console.WriteLine($"ID: {id}, 分類: {category}, " +$"タイトル: {title}, 著者: {author}, 価格: {price}");
}
特定の条件に一致する要素だけを取得することもできます。
var programmingBooks = document.Descendants("book").Where(book =>(string?)book.Attribute("category") == "programming").Select(book => new{Id = (string?)book.Attribute("id"),Title = (string?)book.Element("title"),Price = (int?)book.Element("price")});foreach (var book in programmingBooks){Console.WriteLine($"{book.Id}: {book.Title} {book.Price}円");}
3-5. XDocumentを使うメリット・デメリット
XDocumentのメリットは、LINQを利用してXMLを直感的に検索・加工できることです。要素や属性を型変換する機能もあり、コードを簡潔に書けます。
主なメリットは次のとおりです。
LINQを利用できる
コードが短く読みやすい
XMLの作成や編集にも使いやすい
要素を
intやdecimalなどへ変換できる名前空間を明示的に扱いやすい
一方、XML全体をメモリ上に展開するため、非常に大きなXMLではメモリ使用量が増えます。また、XPathを利用するには追加の名前空間や拡張メソッドが必要です。
通常規模のXMLを読み取る場合は、最も扱いやすい選択肢です。
4. XmlDocumentでXMLをParseする方法
4-1. XmlDocumentとは
XmlDocumentは、XMLをDOM形式で扱うクラスです。名前空間はSystem.Xmlです。
using System.Xml;XML全体をノードのツリーとしてメモリ上に保持します。要素はXmlElement、属性はXmlAttributeとして扱い、XPathによる検索が可能です。
4-2. XmlDocument.LoadでXMLファイルを読み込む
XMLファイルを読み込む場合は、Loadメソッドを使用します。
using System;using System.Xml;class Program{static void Main(){var document = new XmlDocument();document.Load("books.xml");
Console.WriteLine(document.DocumentElement?.Name);}
}
実行結果は次のとおりです。
booksDocumentElementは、XMLのルート要素を表します。
4-3. XmlDocument.LoadXmlでXML文字列を読み込む
XML文字列を読み込む場合は、LoadXmlメソッドを使用します。
string xml = """<book id="1"><title>C#入門</title><price>2800</price></book>""";var document = new XmlDocument();document.LoadXml(xml);
XmlNode? titleNode = document.SelectSingleNode("/book/title");
Console.WriteLine(titleNode?.InnerText);
実行結果は次のとおりです。
C#入門Loadはファイルやストリームなどを読み込むメソッド、LoadXmlはXML文字列を読み込むメソッドです。名前が似ているため注意しましょう。
4-4. SelectSingleNode・SelectNodesで要素を取得する
SelectSingleNodeは、XPathに一致する最初のノードを取得します。
var document = new XmlDocument();document.Load("books.xml");XmlNode? titleNode =document.SelectSingleNode("/books/book[@id='1']/title");
if (titleNode is not null){Console.WriteLine(titleNode.InnerText);}
SelectNodesは、XPathに一致する複数のノードを取得します。
XmlNodeList? bookNodes =document.SelectNodes("/books/book");if (bookNodes is not null){foreach (XmlNode bookNode in bookNodes){string id =bookNode.Attributes?["id"]?.Value ?? "";
string category =bookNode.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 title =bookNode.SelectSingleNode("title")?.InnerText ?? "";string author =bookNode.SelectSingleNode("author")?.InnerText ?? "";string price =bookNode.SelectSingleNode("price")?.InnerText ?? "";Console.WriteLine($"ID: {id}, 分類: {category}, " +$"タイトル: {title}, 著者: {author}, 価格: {price}");}
}
価格が2,600円以上の書籍を取得するXPathは、次のように書けます。
XmlNodeList? expensiveBooks =document.SelectNodes("/books/book[price >= 2600]");4-5. XmlDocumentを使うメリット・デメリット
XmlDocumentの大きなメリットは、XPathを使ってノードを検索できることです。複雑な条件でも、XPathの式として簡潔に表現できます。
主なメリットは次のとおりです。
XPathを利用しやすい
XMLの追加、変更、削除に対応できる
古い.NET Frameworkのシステムでも広く利用されている
DOMとして任意のノードへアクセスできる
デメリットは、APIがXDocumentより古く、型変換やLINQとの連携に手間がかかることです。また、XML全体をメモリに保持するため、大容量XMLには向きません。
既存システムがXmlDocumentを使用している場合や、XPathを中心にXMLを検索したい場合に適しています。
5. XmlReaderでXMLをParseする方法
5-1. XmlReaderとは
XmlReaderは、XMLを先頭から順番に読み取るストリーム型のAPIです。
XDocumentやXmlDocumentのようにXML全体をメモリへ展開せず、現在位置のノードだけを処理します。そのため、大容量XMLを少ないメモリで読み取れます。
ただし、基本的に前へ戻れません。すでに通過した要素を再び参照するには、XMLを読み直すか、必要なデータを別の変数やコレクションへ保存する必要があります。
5-2. XmlReader.CreateでXMLを読み込む
XMLファイルを読み込むには、XmlReader.Createを使用します。
using System;using System.Xml;class Program{static void Main(){using XmlReader reader = XmlReader.Create("books.xml");
while (reader.Read()){Console.WriteLine($"種類: {reader.NodeType}, 名前: {reader.Name}");}}
}
usingを付けることで、処理終了時にファイルやストリームなどのリソースが解放されます。
XML文字列を読み込む場合は、StringReaderを使用します。
using System.IO;using System.Xml;string xml = """<book id="1"><title>C#入門</title></book>""";
using var stringReader = new StringReader(xml);using XmlReader reader = XmlReader.Create(stringReader);
while (reader.Read()){Console.WriteLine($"{reader.NodeType}: {reader.Name}");}
5-3. Readメソッドで要素を順番に読み取る
Readメソッドを呼び出すと、XML内の次のノードへ移動します。
要素の開始位置はXmlNodeType.Element、テキストはXmlNodeType.Text、要素の終了位置はXmlNodeType.EndElementとして判定できます。
using XmlReader reader = XmlReader.Create("books.xml");while (reader.Read()){if (reader.NodeType == XmlNodeType.Element &&reader.Name == "title"){string title = reader.ReadElementContentAsString();Console.WriteLine(title);}}
実行結果は次のとおりです。
C#入門SQL入門属性はGetAttributeで取得できます。
using XmlReader reader = XmlReader.Create("books.xml");while (reader.Read()){if (reader.NodeType == XmlNodeType.Element &&reader.Name == "book"){string id = reader.GetAttribute("id") ?? "";string category =reader.GetAttribute("category") ?? "";
Console.WriteLine($"ID: {id}, 分類: {category}");}
}
5-4. 大容量XMLを処理するサンプルコード
大容量XMLから書籍情報を1件ずつ読み取るサンプルは、次のとおりです。
using System;using System.Xml;class Program{static void Main(){var settings = new XmlReaderSettings{DtdProcessing = DtdProcessing.Prohibit,XmlResolver = null,IgnoreComments = true,IgnoreWhitespace = true};
using XmlReader reader =XmlReader.Create("books.xml", settings);while (reader.Read()){if (reader.NodeType != XmlNodeType.Element ||reader.Name != "book"){continue;}string id = reader.GetAttribute("id") ?? "";string category =reader.GetAttribute("category") ?? "";string title = "";string author = "";decimal? price = null;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;}}Console.WriteLine($"ID: {id}, 分類: {category}, " +$"タイトル: {title}, 著者: {author}, 価格: {price}");}}
}
この方法では、book要素を1件ずつ処理できます。すべての書籍情報をリストに保存しなければ、ファイルのサイズが大きくなってもメモリ使用量を抑えられます。
読み取ったデータをデータベースへ保存する場合も、一定件数ごとに書き込むことでメモリ消費を抑えられます。
5-5. XmlReaderを使うメリット・デメリット
XmlReaderのメリットは、処理速度とメモリ効率です。
XML全体をメモリへ読み込まない
大容量ファイルを処理しやすい
必要な要素だけを順番に処理できる
読み取り専用の処理に適している
一方、現在のノード位置を意識してコードを書く必要があります。要素の階層が複雑になるほど、XDocumentよりコードが長くなりやすい点がデメリットです。
また、前のノードへ自由に戻ったり、XML内の任意の場所へ直接移動したりする用途には適していません。
6. XDocument・XmlDocument・XmlReaderの違い
6-1. 3つのXML読み取り方法の比較表
| 比較項目 | XDocument | XmlDocument | XmlReader |
| 読み取り方式 | XML全体をツリー化 | XML全体をDOM化 | 先頭から順番に読み取る |
| LINQ | 利用しやすい | 直接は利用しにくい | 基本的に利用しない |
| XPath | 拡張メソッドで利用可能 | 標準的に利用可能 | 利用不可 |
| メモリ使用量 | XMLサイズに応じて増える | XMLサイズに応じて増える | 少ない |
| ランダムアクセス | 可能 | 可能 | 不可 |
| XMLの編集 | 可能 | 可能 | 不向き |
| コードの書きやすさ | 高い | 普通 | やや難しい |
| 大容量XML | 不向き | 不向き | 適している |
| 初心者向け | おすすめ | XPath利用時におすすめ | ストリーム処理の理解が必要 |
6-2. 初心者におすすめなのはXDocument
初心者がC#でXMLをParseする場合は、まずXDocumentを検討するとよいでしょう。
ElementやAttributeの意味が分かりやすく、LINQを使って必要なデータを簡潔に取り出せます。
XDocument document = XDocument.Load("books.xml");var titles = document.Descendants("title").Select(element => element.Value);
foreach (string title in titles){Console.WriteLine(title);}
XMLの読み取りだけでなく、要素の追加や保存にも対応しているため、一般的なアプリケーションで幅広く利用できます。
6-3. XPathを使いたい場合はXmlDocument
XPathを中心にXMLを検索する場合は、XmlDocumentが扱いやすい選択肢です。
var document = new XmlDocument();document.Load("books.xml");XmlNodeList? nodes =document.SelectNodes("/books/book[@category='programming']/title");
if (nodes is not null){foreach (XmlNode node in nodes){Console.WriteLine(node.InnerText);}}
既存のXPath式が用意されているシステムや、XMLノードをXPathで細かく絞り込みたい処理に適しています。
6-4. 大容量XMLを高速に処理したい場合はXmlReader
数百MB以上のXMLや、要素数が非常に多いXMLでは、XmlReaderが有力です。
XDocumentとXmlDocumentはXML全体をメモリへ展開するため、元のファイルサイズより多くのメモリを消費する可能性があります。
XmlReaderなら必要なノードだけを読み取り、その場で処理できます。
using XmlReader reader = XmlReader.Create("large-data.xml");while (reader.Read()){if (reader.NodeType == XmlNodeType.Element &&reader.Name == "item"){string id = reader.GetAttribute("id") ?? "";
// 1件ずつ保存・集計するConsole.WriteLine(id);}
}
6-5. 用途別の使い分け方
用途別の目安は次のとおりです。
| 用途 | 適したクラス |
| 小規模から中規模のXMLを読み取る | XDocument |
| LINQで検索・加工する | XDocument |
| XMLを作成・編集する | XDocumentまたはXmlDocument |
| XPathでノードを検索する | XmlDocument |
| 古い.NET Frameworkのコードを保守する | XmlDocument |
| 大容量XMLを読み取る | XmlReader |
| XMLを1件ずつ処理する | XmlReader |
| メモリ使用量を抑える | XmlReader |
特別な要件がなければXDocumentから始め、ファイルサイズやXPathの必要性に応じてほかの方法を選ぶのが分かりやすい使い分け方です。
7. C#でXMLの要素・属性・値を取得するサンプル
7-1. 要素の値を取得する方法
XDocumentで子要素の値を取得する場合は、Elementを使用します。
XDocument document = XDocument.Load("books.xml");XElement? firstBook =document.Root?.Element("book");
string title =firstBook?.Element("title")?.Value ?? "";
Console.WriteLine(title);
明示的なキャストを使用する方法もあります。
string title =(string?)firstBook?.Element("title") ?? "";decimal? price =(decimal?)firstBook?.Element("price");
値が存在しない可能性がある場合は、string?やdecimal?などのnullable型で受け取ると安全です。
7-2. 属性の値を取得する方法
XDocumentでは、Attributeで属性を取得します。
XElement? firstBook =document.Root?.Element("book");string id =(string?)firstBook?.Attribute("id") ?? "";
string category =(string?)firstBook?.Attribute("category") ?? "";
Console.WriteLine($"ID: {id}");Console.WriteLine($"分類: {category}");
XmlDocumentでは、Attributesまたは属性名を指定したインデクサーを使用します。
XmlNode? bookNode =document.SelectSingleNode("/books/book");string id =bookNode?.Attributes?["id"]?.Value ?? "";
7-3. 複数要素をforeachで取得する方法
XDocumentで複数のbook要素を処理するサンプルです。
XDocument document = XDocument.Load("books.xml");foreach (XElement book in document.Descendants("book")){string id =(string?)book.Attribute("id") ?? "";
string title =(string?)book.Element("title") ?? "";decimal? price =(decimal?)book.Element("price");Console.WriteLine($"{id}: {title}({price}円)");
}
匿名型や独自クラスへ変換することもできます。
var books = document.Descendants("book").Select(book => new{Id = (int?)book.Attribute("id"),Title = (string?)book.Element("title"),Author = (string?)book.Element("author"),Price = (decimal?)book.Element("price")}).ToList();独自クラスへ格納する場合は、次のように記述できます。
public class Book{public int Id { get; init; }public string Title { get; init; } = "";public string Author { get; init; } = "";public decimal Price { get; init; }}List<Book> books = document.Descendants("book").Select(book => new Book{Id = (int?)book.Attribute("id") ?? 0,Title = (string?)book.Element("title") ?? "",Author = (string?)book.Element("author") ?? "",Price = (decimal?)book.Element("price") ?? 0}).ToList();7-4. 階層が深いXMLを読み取る方法
次のように階層が深いXMLを考えます。
<company><departments><department id="development"><employees><employee><name>山田太郎</name></employee></employees></department></departments></company>各階層を明示的にたどる場合は、Elementを続けて使用します。
XDocument document = XDocument.Load("company.xml");string employeeName =document.Root?.Element("departments")?.Element("department")?.Element("employees")?.Element("employee")?.Element("name")?.Value?? "";
Console.WriteLine(employeeName);
階層の途中を問わず、すべてのname要素を検索したい場合はDescendantsを使用します。
IEnumerable<string> names = document.Descendants("employee").Select(employee =>(string?)employee.Element("name") ?? "");foreach (string name in names){Console.WriteLine(name);}
ただし、Descendants("name")のように広範囲を検索すると、別の用途のname要素まで取得する可能性があります。XMLの構造が決まっている場合は、対象となる親要素も指定すると安全です。
XMLで名前空間が使われている場合は、要素名だけでは取得できない点にも注意が必要です。
<catalog xmlns="https://example.com/books"><book><title>C#入門</title></book></catalog>この場合は、名前空間を指定します。
XDocument document = XDocument.Load("catalog.xml");XNamespace ns = "https://example.com/books";
string title =(string?)document.Descendants(ns + "book").FirstOrDefault()?.Element(ns + "title")?? "";
Console.WriteLine(title);
8. C#でXMLをParseするときによくあるエラーと対処法
8-1. XMLの形式が不正な場合
XMLの開始タグと終了タグが一致していない場合や、属性値が引用符で囲まれていない場合は、XmlExceptionが発生します。
不正なXMLの例は次のとおりです。
<book><title>C#入門</book>titleの終了タグがないため、正しくParseできません。
using System.Xml;using System.Xml.Linq;try{XDocument document = XDocument.Parse(xml);}catch (XmlException ex){Console.WriteLine($"XMLの形式が不正です: {ex.Message}");Console.WriteLine($"行番号: {ex.LineNumber}");Console.WriteLine($"行内位置: {ex.LinePosition}");}
XmlExceptionのLineNumberとLinePositionを確認すると、エラー箇所を特定しやすくなります。
XML内で&を文字として使う場合は、&と記述する必要があります。
<company>A&B株式会社</company>8-2. ファイルパスが間違っている場合
指定したXMLファイルが存在しない場合は、FileNotFoundExceptionやDirectoryNotFoundExceptionが発生します。
読み込み前にファイルの存在を確認できます。
string filePath = Path.Combine(AppContext.BaseDirectory,"books.xml");if (!File.Exists(filePath)){Console.WriteLine($"XMLファイルが見つかりません: {filePath}");
return;
}
XDocument document = XDocument.Load(filePath);
相対パスは、ソースコードのあるディレクトリではなく、アプリケーションのカレントディレクトリを基準に解決されます。
問題を確認するときは、完全なパスを出力すると分かりやすくなります。
Console.WriteLine(Path.GetFullPath("books.xml"));8-3. 要素や属性が存在しない場合
存在しない要素に対して直接Valueへアクセスすると、NullReferenceExceptionが発生する可能性があります。
次の書き方は安全ではありません。
string title =document.Root!.Element("book")!.Element("title")!.Value;要素や属性が存在しない可能性を考慮し、null条件演算子とnull合体演算子を使用します。
string title =document.Root?.Element("book")?.Element("title")?.Value?? "タイトル未設定";必須要素が存在しない場合にエラーとして扱いたい場合は、明示的に検証します。
XElement? titleElement =document.Root?.Element("book")?.Element("title");if (titleElement is null){throw new InvalidDataException("必須要素titleが存在しません。");}
string title = titleElement.Value;
8-4. 文字コードで読み込みエラーになる場合
XMLファイルの実際の文字コードと、XML宣言の文字コードが一致していない場合、文字化けや読み込みエラーの原因になります。
<?xml version="1.0" encoding="utf-8"?>この宣言がある場合、ファイル自体もUTF-8で保存する必要があります。
ファイルの文字コードを明示して読み込む場合は、StreamReaderを使用できます。
using System.Text;using System.Xml.Linq;using var streamReader = new StreamReader("books.xml",Encoding.UTF8,detectEncodingFromByteOrderMarks: true);
XDocument document = XDocument.Load(streamReader);
ただし、XML宣言やBOMを利用して文字コードを自動判定させたい場合は、文字列へ変換せず、ファイルやバイトストリームをXMLパーサーへ直接渡す方法が適しています。
using FileStream stream = File.OpenRead("books.xml");XDocument document = XDocument.Load(stream);
APIから受信したXMLを処理するときも、可能であれば受信したバイト列と文字コード情報を保ったままストリームとして読み込みます。
8-5. null参照を防ぐ書き方
XMLは、想定していた要素や属性が存在しないことがあります。null参照を防ぐには、次の方法が有効です。
string title =(string?)book.Element("title") ?? "";int price =(int?)book.Element("price") ?? 0;
string category =(string?)book.Attribute("category") ?? "未分類";
文字列から数値へ変換する場合は、TryParseを利用する方法もあります。
string? priceText =(string?)book.Element("price");if (decimal.TryParse(priceText, out decimal price)){Console.WriteLine($"{price}円");}else{Console.WriteLine("価格を数値に変換できません。");}
nullを空文字や0へ置き換えるだけでは、データ不備を見逃す可能性があります。必須項目と任意項目を区別し、必須項目がない場合は例外やエラー結果を返す設計が重要です。
9. C#でXMLを安全にParseするための注意点
9-1. 例外処理を入れる
XMLのParseでは、形式不正、ファイル不存在、アクセス権限、文字コードなど、複数の理由で例外が発生します。
using System;using System.IO;using System.Xml;using System.Xml.Linq;try{XDocument document = XDocument.Load("books.xml");
Console.WriteLine("XMLを読み込みました。");
}catch (FileNotFoundException ex){Console.WriteLine($"ファイルが見つかりません: {ex.FileName}");}catch (UnauthorizedAccessException){Console.WriteLine("XMLファイルへのアクセス権限がありません。");}catch (XmlException ex){Console.WriteLine($"XMLの形式が不正です。行: {ex.LineNumber}、" +$"位置: {ex.LinePosition}");}catch (IOException ex){Console.WriteLine($"ファイルの読み込みに失敗しました: {ex.Message}");}
catch (Exception)だけですべてを処理するより、想定される例外を個別に扱うと、原因に応じたメッセージや復旧処理を実装できます。
9-2. 外部XMLを読み込むときのセキュリティ対策
利用者がアップロードしたXMLや、外部サービスから受信したXMLは、内容を信頼せずに処理する必要があります。
特にDTDや外部エンティティの処理を許可すると、意図しないファイルやネットワークリソースへアクセスする危険があります。
外部XMLを読み取る場合は、安全なXmlReaderSettingsを指定します。
using System.Xml;using System.Xml.Linq;var settings = new XmlReaderSettings{DtdProcessing = DtdProcessing.Prohibit,XmlResolver = null,MaxCharactersInDocument = 10_000_000};
using XmlReader reader =XmlReader.Create("external.xml", settings);
XDocument document = XDocument.Load(reader);
XML文字列を安全な設定で読み込む場合は、StringReaderと組み合わせます。
using System.IO;using System.Xml;using System.Xml.Linq;var settings = new XmlReaderSettings{DtdProcessing = DtdProcessing.Prohibit,XmlResolver = null,MaxCharactersInDocument = 10_000_000,MaxCharactersFromEntities = 0};
using var textReader = new StringReader(xml);using XmlReader xmlReader =XmlReader.Create(textReader, settings);
XDocument document = XDocument.Load(xmlReader);
外部XMLでは、次の点も確認しましょう。
DTDを必要としない場合は無効化する
外部リソースの解決を無効化する
読み込める文字数やファイルサイズを制限する
処理時間や要素数に上限を設ける
アップロードファイルの拡張子だけを信用しない
機密情報を例外メッセージやログへ出力しない
9-3. XMLスキーマや形式を事前に確認する
XMLの構造が決まっている場合は、必須要素、属性、データ型などを事前に確認します。
たとえば、書籍XMLに次の条件があるとします。
bookにはid属性が必要titleは空にできないpriceは0以上の数値categoryは決められた値だけを許可する
簡単な検証はC#のコードでも実装できます。
foreach (XElement book in document.Descendants("book")){int? id = (int?)book.Attribute("id");string? title = (string?)book.Element("title");decimal? price = (decimal?)book.Element("price");if (id is null){Console.WriteLine("id属性がありません。");continue;}if (string.IsNullOrWhiteSpace(title)){Console.WriteLine($"ID {id}: titleがありません。");continue;}if (price is null || price < 0){Console.WriteLine($"ID {id}: priceが不正です。");continue;}Console.WriteLine($"ID {id}のデータは正常です。");
}
厳密な形式チェックが必要な場合は、XSDによるスキーマ検証も検討します。単にXMLをParseできたからといって、アプリケーションが必要とするデータ構造になっているとは限りません。
9-4. 大容量XMLではメモリ使用量に注意する
XDocumentとXmlDocumentは、XML全体をメモリ上に保持します。そのため、ファイルサイズが大きい場合は、メモリ不足やガベージコレクションの増加につながる可能性があります。
大容量XMLでは、XmlReaderを使用して1件ずつ処理します。
using XmlReader reader =XmlReader.Create("large-books.xml");while (reader.Read()){if (reader.NodeType == XmlNodeType.Element &&reader.Name == "book"){string id = reader.GetAttribute("id") ?? "";
// 必要な処理を実行し、不要なデータを保持しないProcessBook(id);}
}
すべての結果をList<T>へ保存すると、XmlReaderを使用しても最終的なメモリ使用量は増えます。データベースへの逐次保存、一定件数ごとのバッチ処理、集計結果だけの保持など、読み取り後の処理方法も重要です。
まとめ
C#でXMLをParseする代表的な方法は、XDocument、XmlDocument、XmlReaderの3つです。
一般的なXMLを読み取り、LINQで要素や属性を操作する場合はXDocumentが適しています。コードが短く直感的であるため、初心者にもおすすめです。
XPathを利用してノードを検索したい場合や、既存のDOMベースのコードを保守する場合はXmlDocumentが適しています。
大容量XMLを高速かつ少ないメモリで処理する場合は、XMLを順番に読み取るXmlReaderを選びましょう。
用途別の基本的な選び方は次のとおりです。
通常のXML読み取りは
XDocumentXPathを使う場合は
XmlDocument大容量XMLは
XmlReader
XMLをParseするときは、要素や属性が存在しないケースを考慮し、nullチェックと例外処理を実装することが重要です。また、外部から受信したXMLではDTDや外部エンティティを無効化し、ファイルサイズや文字数にも上限を設けましょう。
3つのクラスの特徴を理解し、XMLのサイズ、検索方法、編集の有無に合わせて使い分けることで、安全で保守しやすいC#のXML処理を実装できます。

