1. はじめに
LINQ が登場してから .NET での XML の扱いはかなり変わりました。以前は NodeList や XmlElement を使って冗長なコードを書かないといけなかったけど、今ではLINQ スタイルで操作を表現できるようになってすごく楽になりました。長い砂利道を抜けて高速道路に入ったような感じです。
LINQ to XML が解決する主なポイントは次の3つです:
- XML の構築や読み取りの文法を簡潔にする。
- ドキュメントのナビゲーションをコレクション操作に似せる。
- XML を任意の LINQ クエリと統合できるようにする。
主要クラス: XDocument, XElement, XAttribute
深みに飛び込む前に、まずこの三つの基本を押さえましょう:
- XDocument — ドキュメント全体のルート、言うなれば本の表紙のようなもの。
- XElement — 個々の XML ノード(要素)。倉庫の中の箱みたいに、他の箱や商品(子要素、属性、テキスト)を含められます。
- XAttribute — 要素の属性。例えば <User id="42" /> の id の部分です。
ビジュアル図
+-----------------+
| XDocument |---------------------+
+-----------------+ |
| (Root XElement)
v |
+-------------+ +-----------------+
| XElement |<-------------| XElement (root) |
+-------------+ +-----------------+
| |
(XAttribute) (nested XElements)
2. LINQ to XML を使った XML の読み取り
例えば学習用アプリ(アドレス帳)で XML ファイルから連絡先を読みたいとします。ファイル構造はこんな感じだとしましょう:
<Contacts>
<Person id="1">
<Name>イワン・イワノフ</Name>
<Email>ivan@example.com</Email>
<Phones>
<Phone type="mobile">+12 999 123-45-67</Phone>
<Phone type="home">+12 812 123-45-67</Phone>
</Phones>
</Person>
<Person id="2">
<Name>マリヤ・ペトロワ</Name>
<Email>maria@example.com</Email>
<Phones>
<Phone type="mobile">+12 921 123-45-67</Phone>
</Phones>
</Person>
</Contacts>
XML をメモリに読み込む
まずプロジェクトに using ディレクティブを追加します:
using System.Xml.Linq;
次にファイルや文字列を読みます:
// xmlString が XML ドキュメントを含んでいるとするか、XDocument.Load("contacts.xml") を使ってもいい
var doc = XDocument.Parse(xmlString);
// Person の一覧を取得
var persons = doc.Root.Elements("Person");
foreach (var person in persons)
{
string name = person.Element("Name")?.Value ?? "名前なし";
string email = person.Element("Email")?.Value ?? "メールなし";
string id = person.Attribute("id")?.Value ?? "idなし";
Console.WriteLine($"名前: {name}, Email: {email}, id: {id}");
}
- doc.Root はルート要素(Contacts)を返します。
- Elements("Person") はすべての <Person> を返します。
- 必要な要素や属性は .Element("...") や .Attribute("...") で取得します。
- 演算子 ?. は要素や属性が見つからない場合のエラーを防ぎます。
アナロジー
XElement を扱うのは入れ子になったマトリョーシカを分解するようなもの:一つ取り出すと中に別のがあって、さらにその中にも...。でも今は冗長な探索コードを書かなくても、スッとクエリで辿れます。
3. XML に対する LINQ クエリの作成
全員の携帯電話番号だけ取り出したい?LINQ スタイルで簡単にできます。
var mobilePhones = doc
.Descendants("Phone") // 任意の階層で Phone を探す
.Where(p => (string)p.Attribute("type") == "mobile")
.Select(p => p.Value);
foreach (string phone in mobilePhones)
{
Console.WriteLine($"モバイル: {phone}");
}
- Descendants("Phone") はドキュメント内のすべての <Phone> を探します。
- Attribute("type") で属性を取得します。
- (string) のキャストは属性から自動的に文字列か null を返します。
便利なトリック
複雑なネストを気にせず複数レベルを横断して検索できるので、古い XmlDocument でやろうとすると面倒な処理がシンプルになります。
4. ランタイムでの XML の作成と変更
XElement / XDocument のコンストラクタを使ってメモリ上で直に XML を作るのがすごく便利です。例えば新しい人物をドキュメントに追加する例(学習用アドレス帳の続き):
// 新しい Person を作る
var newPerson = new XElement("Person",
new XAttribute("id", "3"),
new XElement("Name", "セルゲイ・ノヴィコフ"),
new XElement("Email", "sergey@example.com"),
new XElement("Phones",
new XElement("Phone", new XAttribute("type", "work"), "+12 812 987-65-43")
)
);
// ルートに追加
doc.Root.Add(newPerson);
// ファイルに保存(または文字列に)
doc.Save("contacts.xml");
コメント:
- 要素は「マトリョーシカ」風のコンストラクタでネストできます:最初の引数がタグで、残りが中身(要素、属性、テキストなど)。
- 属性は通常中身の前に置きます(例: <Phone type="work">...)。
5. C# のコレクションとの連携 — LINQ の魔法
普通のオブジェクトのコレクション(例えば List<Person>)を XML にシリアライズしたり、その逆をしたりすることがよくあります。ほとんど一行で書けます:
var people = new List<Person>
{
new Person { Id = 1, Name = "イワン・イワノフ", Email = "ivan@example.com" },
// 他も同様
};
var doc = new XDocument(
new XElement("Contacts",
people.Select(p =>
new XElement("Person",
new XAttribute("id", p.Id),
new XElement("Name", p.Name),
new XElement("Email", p.Email)
)
)
)
);
参考用の Person クラス(全体像):
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
これで C# のコレクションを分かりやすく制御される形で XML に変換できます。
6. 便利なメソッドとテクニック — XElement と XDocument
ナビゲーション
- .Elements("TagName") — 直下の子要素を取得。
- .Descendants("TagName") — 現在の要素の内部のあらゆる階層から要素を取得。
- .Parent — 親要素。
- .Ancestors() — すべての先祖要素(パスを辿るときに便利)。
- .FirstNode, .LastNode, .NextNode, .PreviousNode — 隣接ノードのナビゲーション。
条件付き検索
var personsWithEmail = doc.Root
.Elements("Person")
.Where(p => !string.IsNullOrEmpty((string)p.Element("Email")));
追加と削除
// 既存ユーザーに電話番号を追加
var maria = doc.Root.Elements("Person")
.FirstOrDefault(p => (string)p.Element("Name") == "マリヤ・ペトロワ");
maria?.Element("Phones")?.Add(
new XElement("Phone", new XAttribute("type", "work"), "+12 812 111-22-33")
);
// id でユーザーを削除
doc.Root.Elements("Person")
.Where(p => (string)p.Attribute("id") == "3")
.Remove();
文字列/ファイルに戻す
string xmlText = doc.ToString(); // インデント付き
doc.Save("contacts.xml");
7. 役立つ細かい注意点
LINQ to XML と他の .NET アプローチの比較
| アプローチ | いつ使うか | 利点 | 欠点 |
|---|---|---|---|
|
古いコードやレガシーな場合 | XPath との統合がしやすい | コード量が多く、API がやや扱いにくい |
|
オブジェクトのシリアライズ用 | C# ↔ XML のシリアライズがシンプル | 柔軟性が低い |
|
その他のほとんど全て:解析や編集に最適 | 直感的で簡潔、強力、LINQ と相性が良い | メモリ消費が若干増えることがある |
実プロジェクトや面接での使いどころ
- 設定ファイルの解析(C# クラスにきれいに対応しない場合)。
- データのインポート/エクスポート(ユーザーデータの入出力など)。
- マイグレーションや「汚れた」複雑な XML 構造の処理。
- 素早いフォーマット変換(ある XML 形式から別の形式への変換など)。
「なんでこれを知っておく必要があるの?」って思うかもしれませんが、面接で複雑な XML を変換・解析する課題を出されることがあり、LINQ to XML を知っていれば古い API よりずっとスマートに解けます。
8. LINQ to XML を使うときのよくあるミス
ミスその1:存在しない要素にチェックなしでアクセスする。
要素が存在しない可能性がある場合は ?. を使うかあらかじめ null チェックを入れましょう。そうしないと実行時に NullReferenceException を投げてクラッシュします。
ミスその2:属性と要素を混同する。
属性と要素は別物です。データがどちらに入っているかで取得方法が変わります。例えば:
<User login="admin"><Status>active</Status></User>
属性 login を取りたいなら .Attribute("login")、タグ <Status> の中身が欲しいなら .Element("Status") を使います。
ミスその3:.Remove() の挙動を誤解する。
.Remove() はメモリ上の XML 構造を即座に変更します。何も返さないので「削除した要素を返してくれるだろう」と期待してはいけません。一度削除すると元に戻せない(元に戻すなら自分でコピーを取る必要があります)。
GO TO FULL VERSION