CodeGym /コース /C# SELF /LINQ to XML: XDocument

LINQ to XML: XDocument, XElement

C# SELF
レベル 48 , レッスン 3
使用可能

1. はじめに

LINQ が登場してから .NET での XML の扱いはかなり変わりました。以前は NodeListXmlElement を使って冗長なコードを書かないといけなかったけど、今では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. 便利なメソッドとテクニック — XElementXDocument

ナビゲーション

  • .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 アプローチの比較

アプローチ いつ使うか 利点 欠点
XmlDocument
古いコードやレガシーな場合 XPath との統合がしやすい コード量が多く、API がやや扱いにくい
XmlSerializer
オブジェクトのシリアライズ用 C# ↔ XML のシリアライズがシンプル 柔軟性が低い
LINQ to 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 構造を即座に変更します。何も返さないので「削除した要素を返してくれるだろう」と期待してはいけません。一度削除すると元に戻せない(元に戻すなら自分でコピーを取る必要があります)。

2
タスク
C# SELF, レベル 48, レッスン 3
ロック未解除
XMLドキュメントからデータを抽出する
XMLドキュメントからデータを抽出する
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION