C# XDocumentの使い方を基礎から解説|XMLの読み込み・作成・検索・更新までわかる入門ガイド

はじめに

C#でXMLを扱うときに便利なのが、LINQ to XMLで提供されているXDocumentです。XDocumentを使うと、XMLファイルの読み込み、XML文字列の解析、要素や属性の取得、条件検索、追加、更新、削除、保存といった処理を、C#らしいシンプルなコードで記述できます。

従来のXmlDocumentでもXML操作は可能ですが、XDocumentはLINQと組み合わせて扱いやすい点が大きな特徴です。たとえば、特定の要素だけを検索したり、属性の値を条件にデータを絞り込んだりする処理を、直感的に書くことができます。

この記事では、C#のXDocumentの使い方を基礎から解説します。XMLの読み込み、値の取得、検索、新規作成、更新、保存、名前空間付きXMLの扱い方まで、入門者にもわかりやすいようにサンプルコード付きで紹介します。

1. C#のXDocumentとは?XML操作を簡単に扱えるLINQ to XMLの基本

XDocumentは、C#でXMLドキュメント全体を扱うためのクラスです。LINQ to XMLという仕組みの一部として提供されており、XMLをオブジェクトとして読み込んだり、LINQを使って検索したりできます。

XMLは設定ファイル、データ交換、外部システム連携などで使われることがあります。C#でXMLを処理する場合、XDocumentを理解しておくと、読み込みから編集までの処理を効率よく実装できます。

1-1. XDocumentでできること

XDocumentを使うと、主に次のようなXML操作ができます。

  • XMLファイルを読み込む

  • XML文字列を解析する

  • ルート要素を取得する

  • 子要素や孫要素を取得する

  • 要素の値を取得する

  • 属性の値を取得する

  • LINQで条件検索する

  • 新しいXMLを作成する

  • 要素や属性を追加する

  • 要素や属性を更新する

  • 要素や属性を削除する

  • XMLファイルとして保存する

  • 名前空間付きXMLを扱う

たとえば、次のようなXMLから商品名を取得する処理も簡単に書けます。

C#
using System;
using System.Xml.Linq;

class Program
{
static void Main()
{
string xml = @"
<products>
<product>
<name>ノートPC</name>
<price>120000</price>
</product>
</products>";

XDocument doc = XDocument.Parse(xml);

string? name = doc.Root?
.Element("product")?
.Element("name")?
.Value;

Console.WriteLine(name);
}
}

XDocumentでは、XMLの構造をたどりながら目的の要素にアクセスできます。?.を使うことで、要素が存在しない場合でも例外を避けながら安全に取得できます。

1-2. XDocumentとXElementの違い

XDocumentとよく一緒に使われるのがXElementです。どちらもLINQ to XMLで重要なクラスですが、役割が異なります。

XDocumentはXMLドキュメント全体を表します。XML宣言、ルート要素、コメント、DOCTYPEなど、ドキュメント単位の情報を含めて扱います。

一方、XElementはXMLの要素を表します。たとえば、<product><name>のようなタグ単位のデータを扱うときに使います。

C#
XDocument doc = new XDocument(
new XElement("products",
new XElement("product",
new XElement("name", "ノートPC"),
new XElement("price", 120000)
)
)
);

このコードでは、XML全体を表すのがXDocumentで、その中にあるproductsproductnamepriceXElementです。

XML全体を読み込んだり保存したりする場合はXDocumentを使い、個々の要素を操作する場合はXElementを使うと考えるとわかりやすいです。

1-3. XmlDocumentとの違いとXDocumentを使うメリット

C#でXMLを扱う方法には、XmlDocumentを使う方法もあります。XmlDocumentはDOMベースでXMLを操作する古くからあるクラスです。

一方、XDocumentはLINQ to XMLとして提供されており、LINQと相性がよいのが特徴です。

XDocumentを使うメリットは次のとおりです。

  • LINQで検索や絞り込みが書きやすい

  • コードが比較的シンプルになる

  • 要素や属性を直感的に作成できる

  • XMLの読み込み、編集、保存を一貫して扱える

  • C#のオブジェクト操作に近い感覚でXMLを扱える

たとえば、属性id"p001"の商品を検索する処理は、XDocumentでは次のように書けます。

C#
XElement? product = doc
.Descendants("product")
.FirstOrDefault(x => (string?)x.Attribute("id") == "p001");

DescendantsWhereFirstOrDefaultなどのLINQメソッドを使えるため、条件検索が読みやすくなります。

1-4. XDocumentを使うために必要な名前空間

XDocumentを使うには、次の名前空間を追加します。

C#
using System.Xml.Linq;

また、LINQのWhereSelectFirstOrDefaultなどを使う場合は、通常次の名前空間も使います。

C#
using System.Linq;

最近のC#プロジェクトでは、暗黙的なusingによりSystem.Linqが自動的に使える場合もあります。ただし、環境によっては明示的に追加したほうがわかりやすいです。

基本的な記述は次のようになります。

C#
using System;
using System.Linq;
using System.Xml.Linq;

class Program
{
static void Main()
{
XDocument doc = XDocument.Load("sample.xml");
Console.WriteLine(doc.Root?.Name);
}
}

2. XDocumentの基本構造を理解しよう

XDocumentを使いこなすには、XMLの基本構造を理解しておくことが大切です。XMLは、要素、属性、値、階層構造によってデータを表現します。

2-1. XMLドキュメント・ルート要素・子要素・属性の関係

XMLには、必ず1つのルート要素があります。ルート要素の中に子要素や孫要素を配置し、必要に応じて属性を付けます。

たとえば、次のXMLを見てみましょう。

XML
<?xml version="1.0" encoding="utf-8"?>
<products>
<product id="p001" category="pc">
<name>ノートPC</name>
<price>120000</price>
</product>
<product id="p002" category="accessory">
<name>マウス</name>
<price>3000</price>
</product>
</products>

このXMLでは、productsがルート要素です。productsの直下にproduct要素があり、各product要素にはid属性とcategory属性があります。

さらに、product要素の中にはname要素とprice要素があります。このように、XMLは階層構造でデータを表現します。

2-2. XDocument・XElement・XAttributeの役割

LINQ to XMLでは、XMLの各部分を次のクラスで扱います。

クラス役割
XDocumentXMLドキュメント全体を表す
XElementXML要素を表す
XAttributeXML属性を表す
XDeclarationXML宣言を表す
XNamespaceXML名前空間を表す

たとえば、次のコードではXDocumentXElementXAttributeを使ってXMLを作成しています。

C#
XDocument doc = new XDocument(
new XElement("product",
new XAttribute("id", "p001"),
new XElement("name", "ノートPC"),
new XElement("price", 120000)
)
);

出力されるXMLは次のようになります。

XML
<product id="p001">
<name>ノートPC</name>
<price>120000</price>
</product>

XAttributeは要素に付く追加情報、XElementはタグそのもの、XDocumentはXML全体を表すと考えると整理しやすいです。

2-3. サンプルXMLで全体像を確認する

この記事では、主に次のような商品データのXMLを使って説明します。

XML
<?xml version="1.0" encoding="utf-8"?>
<products>
<product id="p001" category="pc">
<name>ノートPC</name>
<price>120000</price>
<stock>10</stock>
</product>
<product id="p002" category="accessory">
<name>マウス</name>
<price>3000</price>
<stock>50</stock>
</product>
<product id="p003" category="accessory">
<name>キーボード</name>
<price>8000</price>
<stock>20</stock>
</product>
</products>

このXMLでは、productsの中に複数のproductがあり、それぞれの商品がidcategorynamepricestockを持っています。

C#のXDocumentを使うと、このようなXMLから特定の商品だけを検索したり、価格や在庫数を更新したりできます。

3. XDocumentでXMLを読み込む方法

XDocumentでXMLを扱う最初のステップは、XMLを読み込むことです。XMLファイルを読み込む場合はXDocument.Load、XML文字列を読み込む場合はXDocument.Parseを使います。

3-1. XMLファイルを読み込む:XDocument.Load

XMLファイルを読み込むには、XDocument.Loadを使います。

C#
using System;
using System.Xml.Linq;

class Program
{
static void Main()
{
XDocument doc = XDocument.Load("products.xml");

Console.WriteLine(doc.Root?.Name);
}
}

products.xmlが次のような内容だとします。

XML
<products>
<product id="p001">
<name>ノートPC</name>
<price>120000</price>
</product>
</products>

この場合、doc.Root?.Nameproductsになります。

ファイルパスには、相対パスまたは絶対パスを指定できます。実行ファイルの場所とXMLファイルの場所が異なる場合は、パスの指定に注意しましょう。

C#
XDocument doc = XDocument.Load(@"C:\data\products.xml");

3-2. XML文字列を読み込む:XDocument.Parse

XMLが文字列として存在する場合は、XDocument.Parseを使います。

C#
string xml = @"
<products>
<product id=""p001"">
<name>ノートPC</name>
<price>120000</price>
</product>
</products>";

XDocument doc = XDocument.Parse(xml);

string? name = doc.Root?
.Element("product")?
.Element("name")?
.Value;

Console.WriteLine(name);

Parseは、APIから取得したXML文字列や、プログラム内で生成したXML文字列を読み込むときに便利です。

ただし、文字列がXMLとして正しい形式でない場合は例外が発生します。そのため、外部から受け取ったXMLを扱う場合は例外処理を入れておくと安全です。

3-3. 読み込み時によく使うLoadOptions

XDocument.LoadXDocument.Parseでは、LoadOptionsを指定できます。

よく使うオプションは次のとおりです。

オプション内容
LoadOptions.None既定の読み込み
LoadOptions.PreserveWhitespace空白を保持する
LoadOptions.SetLineInfo行番号情報を保持する
LoadOptions.SetBaseUriベースURI情報を保持する

たとえば、XMLの空白や改行をできるだけ保持したい場合は、次のように指定します。

C#
XDocument doc = XDocument.Load(
"products.xml",
LoadOptions.PreserveWhitespace
);

XMLの読み込みエラーが発生したときに行番号を確認したい場合は、SetLineInfoを使います。

C#
XDocument doc = XDocument.Load(
"products.xml",
LoadOptions.SetLineInfo
);

通常のXML操作ではLoadOptions.Noneでも問題ありません。整形やデバッグの都合で必要になったときに、適切なオプションを指定するとよいでしょう。

3-4. XML読み込み時の例外処理

XMLファイルが存在しない、XMLの形式が不正、アクセス権限がないといった場合、読み込み時に例外が発生します。

代表的な例外には次のようなものがあります。

例外主な原因
FileNotFoundExceptionファイルが存在しない
UnauthorizedAccessExceptionファイルへのアクセス権限がない
XmlExceptionXMLの形式が不正
IOExceptionファイル入出力エラー

実際のコードでは、次のように例外処理を入れておくと安全です。

C#
using System;
using System.IO;
using System.Xml;
using System.Xml.Linq;

class Program
{
static void Main()
{
try
{
XDocument doc = XDocument.Load("products.xml");
Console.WriteLine("XMLを読み込みました。");
Console.WriteLine(doc.Root?.Name);
}
catch (FileNotFoundException)
{
Console.WriteLine("XMLファイルが見つかりません。");
}
catch (XmlException ex)
{
Console.WriteLine("XMLの形式が正しくありません。");
Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("エラーが発生しました。");
Console.WriteLine(ex.Message);
}
}
}

外部ファイルや外部APIのXMLを扱う場合は、想定外のデータが来ることもあります。XDocumentでXMLを読み込む処理では、例外処理を入れておくのが基本です。

4. XDocumentでXMLの値を取得する方法

XMLを読み込んだら、次は要素や属性の値を取得します。XDocumentでは、RootElementElementsAttributeなどを使ってXMLの中身にアクセスします。

4-1. ルート要素を取得する

XMLのルート要素を取得するには、Rootプロパティを使います。

C#
XDocument doc = XDocument.Load("products.xml");

XElement? root = doc.Root;

Console.WriteLine(root?.Name);

次のXMLの場合、ルート要素はproductsです。

XML
<products>
<product>
<name>ノートPC</name>
</product>
</products>

doc.RootXElement?として扱うのが安全です。通常、正しいXMLであればルート要素は存在しますが、処理の安全性を高めるためにnullチェックを意識しましょう。

4-2. 子要素・孫要素を取得する

直下の子要素を取得するにはElementまたはElementsを使います。

Elementは、指定した名前の最初の要素を取得します。

C#
XElement? product = doc.Root?.Element("product");

Elementsは、指定した名前の要素をすべて取得します。

C#
IEnumerable<XElement> products = doc.Root?.Elements("product")
?? Enumerable.Empty<XElement>();

foreach (XElement product in products)
{
Console.WriteLine(product.Element("name")?.Value);
}

孫要素を取得する場合は、Elementをつなげて書けます。

C#
string? name = doc.Root?
.Element("product")?
.Element("name")?
.Value;

Console.WriteLine(name);

XMLの階層が深い場合でも、Elementを順番にたどることで目的の要素にアクセスできます。

4-3. 要素の値を取得する

要素の値を取得するには、Valueプロパティを使います。

C#
XElement? nameElement = doc.Root?
.Element("product")?
.Element("name");

string? name = nameElement?.Value;

Console.WriteLine(name);

Valueは文字列として値を返します。数値として扱いたい場合は、int.Parsedecimal.Parseなどで変換します。

C#
string? priceText = doc.Root?
.Element("product")?
.Element("price")?
.Value;

if (int.TryParse(priceText, out int price))
{
Console.WriteLine(price);
}

外部XMLでは値が空だったり、数値に変換できない文字列が入っていたりすることがあります。そのため、数値変換にはTryParseを使うと安全です。

4-4. 属性の値を取得する

属性の値を取得するには、Attributeメソッドを使います。

C#
XElement? product = doc.Root?.Element("product");

string? id = product?.Attribute("id")?.Value;

Console.WriteLine(id);

次のXMLでは、id属性の値はp001です。

XML
<product id="p001">
<name>ノートPC</name>
</product>

属性値も要素と同じように文字列として取得されます。数値や日付として使いたい場合は、必要に応じて変換します。

また、LINQ to XMLでは明示的な型変換を使う書き方もできます。

C#
string? id = (string?)product?.Attribute("id");
int? price = (int?)product?.Element("price");

この書き方では、要素や属性が存在しない場合にnullを返せるため、シンプルに書けることがあります。

4-5. 値が存在しない場合の安全な取得方法

XMLの構造が常に同じとは限りません。要素や属性が存在しない可能性がある場合は、nullチェックを入れながら取得します。

C#
string name = doc.Root?
.Element("product")?
.Element("name")?
.Value
?? "名称未設定";

Console.WriteLine(name);

属性も同じように、存在しない場合の既定値を設定できます。

C#
string category = doc.Root?
.Element("product")?
.Attribute("category")?
.Value
?? "未分類";

Console.WriteLine(category);

複雑なXMLでは、安全に値を取得するためのヘルパーメソッドを用意しておくのも有効です。

C#
static string GetElementValue(XElement element, string name, string defaultValue = "")
{
return element.Element(name)?.Value ?? defaultValue;
}

使用例は次のとおりです。

C#
foreach (XElement product in doc.Root?.Elements("product") ?? Enumerable.Empty<XElement>())
{
string name = GetElementValue(product, "name", "名称未設定");
string price = GetElementValue(product, "price", "0");

Console.WriteLine($"{name}: {price}");
}

XDocumentでXMLを扱うときは、要素や属性が必ず存在する前提で書くのではなく、存在しない場合も考慮しておくことが大切です。

5. XDocumentでXMLを検索する方法

XDocumentの大きなメリットは、LINQを使ってXMLを検索できることです。ElementsDescendantsWhereFirstOrDefaultなどを組み合わせることで、条件に合うXMLデータを簡単に取得できます。

5-1. Elementsで直下の要素を検索する

Elementsは、現在の要素の直下にある子要素を取得します。

C#
IEnumerable<XElement> products = doc.Root?.Elements("product")
?? Enumerable.Empty<XElement>();

foreach (XElement product in products)
{
Console.WriteLine(product.Element("name")?.Value);
}

Elements("product")は、ルート要素の直下にあるproduct要素だけを取得します。階層をまたいで検索するわけではないため、XML構造が明確な場合に適しています。

次のようなXMLでは、products直下のproductだけを取得します。

XML
<products>
<product>
<name>ノートPC</name>
</product>
</products>

直下の要素だけを対象にしたい場合は、Elementsを使うと意図が明確になります。

5-2. Descendantsで階層をまたいで検索する

Descendantsは、指定した名前の要素を階層全体から検索します。

C#
IEnumerable<XElement> names = doc.Descendants("name");

foreach (XElement name in names)
{
Console.WriteLine(name.Value);
}

Descendants("name")は、XML内にあるすべてのname要素を取得します。どの階層にあるかを気にせず検索できるため便利です。

ただし、XML内に同じ名前の要素が複数の意味で使われている場合、意図しない要素まで取得してしまう可能性があります。

たとえば、商品名のnameとメーカー名のnameが混在している場合は注意が必要です。

XML
<products>
<product>
<name>ノートPC</name>
<maker>
<name>Sample Maker</name>
</maker>
</product>
</products>

この場合、Descendants("name")は商品名とメーカー名の両方を取得します。特定の階層に限定したい場合は、Elementsを使って構造を明示したほうが安全です。

5-3. LINQのWhereで条件に一致する要素を検索する

条件に一致する要素を検索するには、LINQのWhereを使います。

C#
var expensiveProducts = doc
.Descendants("product")
.Where(x => (int?)x.Element("price") >= 10000);

foreach (XElement product in expensiveProducts)
{
Console.WriteLine(product.Element("name")?.Value);
}

このコードでは、priceが10000以上の商品だけを取得しています。

FirstOrDefaultを使うと、条件に一致する最初の要素を取得できます。

C#
XElement? product = doc
.Descendants("product")
.FirstOrDefault(x => (string?)x.Element("name") == "マウス");

Console.WriteLine(product?.Element("price")?.Value);

1件だけ取得したい場合はFirstOrDefault、複数件を取得したい場合はWhereを使うとよいでしょう。

5-4. 属性値を条件にして検索する

属性値を条件にして検索する場合は、Attributeを使います。

C#
XElement? product = doc
.Descendants("product")
.FirstOrDefault(x => (string?)x.Attribute("id") == "p001");

Console.WriteLine(product?.Element("name")?.Value);

category属性がaccessoryの商品だけを取得する場合は、次のように書きます。

C#
var accessories = doc
.Descendants("product")
.Where(x => (string?)x.Attribute("category") == "accessory");

foreach (XElement product in accessories)
{
Console.WriteLine(product.Element("name")?.Value);
}

属性はXMLの分類や識別子としてよく使われます。XDocumentでは、属性値による検索もLINQで自然に書けます。

5-5. 複数条件でXMLデータを絞り込む

複数条件で検索する場合は、Whereの中で条件を組み合わせます。

C#
var results = doc
.Descendants("product")
.Where(x =>
(string?)x.Attribute("category") == "accessory" &&
(int?)x.Element("price") <= 5000
);

foreach (XElement product in results)
{
Console.WriteLine(product.Element("name")?.Value);
}

このコードでは、categoryaccessoryで、かつpriceが5000以下の商品を取得しています。

OR条件を使う場合は、||を使います。

C#
var results = doc
.Descendants("product")
.Where(x =>
(string?)x.Element("name") == "マウス" ||
(string?)x.Element("name") == "キーボード"
);

検索結果を匿名型に変換して扱いやすくすることもできます。

C#
var products = doc
.Descendants("product")
.Select(x => new
{
Id = (string?)x.Attribute("id"),
Name = (string?)x.Element("name"),
Price = (int?)x.Element("price") ?? 0,
Stock = (int?)x.Element("stock") ?? 0
})
.Where(x => x.Price >= 5000);

foreach (var product in products)
{
Console.WriteLine($"{product.Id}: {product.Name} - {product.Price}円");
}

このように、C#のXDocumentはLINQと組み合わせることで、XMLデータを扱いやすい形に変換できます。

6. XDocumentでXMLを新規作成する方法

XDocumentはXMLを読み込むだけでなく、新しくXMLを作成することもできます。XElementXAttributeを組み合わせることで、階層構造を持つXMLをC#コードから生成できます。

6-1. XDocumentとXElementでXMLを作成する

基本的なXML作成コードは次のとおりです。

C#
XDocument doc = new XDocument(
new XElement("products",
new XElement("product",
new XElement("name", "ノートPC"),
new XElement("price", 120000)
)
)
);

Console.WriteLine(doc);

出力結果は次のようになります。

XML
<products>
<product>
<name>ノートPC</name>
<price>120000</price>
</product>
</products>

new XElement("要素名", 値)の形式で要素を作成できます。子要素を持たせたい場合は、親要素の中にさらにXElementを入れます。

6-2. 要素を追加する

作成済みのXMLに要素を追加するには、Addメソッドを使います。

C#
XDocument doc = new XDocument(
new XElement("products")
);

doc.Root?.Add(
new XElement("product",
new XElement("name", "マウス"),
new XElement("price", 3000)
)
);

Console.WriteLine(doc);

複数の商品を追加する場合は、ループでAddできます。

C#
var items = new[]
{
new { Id = "p001", Name = "ノートPC", Price = 120000 },
new { Id = "p002", Name = "マウス", Price = 3000 },
new { Id = "p003", Name = "キーボード", Price = 8000 }
};

XDocument doc = new XDocument(new XElement("products"));

foreach (var item in items)
{
doc.Root?.Add(
new XElement("product",
new XAttribute("id", item.Id),
new XElement("name", item.Name),
new XElement("price", item.Price)
)
);
}

C#のオブジェクトやコレクションからXMLを生成したい場合にも、XDocumentは便利です。

6-3. 属性を追加する

属性を追加するには、XAttributeを使います。

C#
XElement product = new XElement("product",
new XAttribute("id", "p001"),
new XAttribute("category", "pc"),
new XElement("name", "ノートPC"),
new XElement("price", 120000)
);

Console.WriteLine(product);

出力結果は次のようになります。

XML
<product id="p001" category="pc">
<name>ノートPC</name>
<price>120000</price>
</product>

既存の要素にあとから属性を追加することもできます。

C#
XElement product = new XElement("product",
new XElement("name", "ノートPC")
);

product.Add(new XAttribute("id", "p001"));

Console.WriteLine(product);

ただし、同じ名前の属性を重複して追加しようとすると例外が発生します。既存属性を変更したい場合は、SetAttributeValueを使うと安全です。

C#
product.SetAttributeValue("id", "p001");

6-4. XML宣言を付けて作成する

XML宣言を付けるには、XDeclarationを使います。

C#
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("products",
new XElement("product",
new XAttribute("id", "p001"),
new XElement("name", "ノートPC"),
new XElement("price", 120000)
)
)
);

Console.WriteLine(doc.Declaration);
Console.WriteLine(doc);

XDeclarationでは、XMLのバージョン、エンコーディング、standalone指定を設定できます。

C#
new XDeclaration("1.0", "utf-8", "yes")

ただし、Console.WriteLine(doc)だけではXML宣言が表示されない場合があります。XML宣言を含めて文字列化したい場合は、doc.Declarationを別途扱う必要があります。

6-5. 作成したXMLを文字列として出力する

作成したXMLを文字列として取得するには、ToStringを使います。

C#
string xmlText = doc.ToString();

Console.WriteLine(xmlText);

XML宣言を含めたい場合は、次のように組み立てることができます。

C#
string xmlText = doc.Declaration + Environment.NewLine + doc.ToString();

Console.WriteLine(xmlText);

インデントを付けずに出力したい場合は、SaveOptions.DisableFormattingを指定します。

C#
string xmlText = doc.ToString(SaveOptions.DisableFormatting);

Console.WriteLine(xmlText);

読みやすさを重視するなら通常のToString、ファイルサイズや通信量を抑えたい場合はDisableFormattingを使うとよいでしょう。

7. XDocumentでXMLを更新・編集する方法

XDocumentでは、読み込んだXMLの要素や属性を簡単に更新できます。値の変更、要素の追加、属性の追加、削除などもXElementXAttributeを通して操作します。

7-1. 要素の値を変更する

要素の値を変更するには、対象の要素を取得してValueを変更します。

C#
XElement? product = doc
.Descendants("product")
.FirstOrDefault(x => (string?)x.Attribute("id") == "p001");

XElement? price = product?.Element("price");

if (price != null)
{
price.Value = "110000";
}

また、SetElementValueを使うと、要素が存在する場合は更新し、存在しない場合は追加できます。

C#
product?.SetElementValue("price", 110000);

SetElementValueは、更新と追加の両方に対応できるため、XML構造が変わる可能性がある場合に便利です。

7-2. 属性の値を変更する

属性の値を変更するには、対象の属性を取得してValueを変更します。

C#
XAttribute? category = product?.Attribute("category");

if (category != null)
{
category.Value = "computer";
}

また、SetAttributeValueを使うと、属性が存在する場合は更新し、存在しない場合は追加できます。

C#
product?.SetAttributeValue("category", "computer");

属性を削除したい場合は、値にnullを指定します。

C#
product?.SetAttributeValue("category", null);

要素の更新にはSetElementValue、属性の更新にはSetAttributeValueを使うと、コードをシンプルにできます。

7-3. 新しい要素を追加する

既存の要素に新しい子要素を追加するには、Addを使います。

C#
product?.Add(new XElement("description", "高性能なノートPCです。"));

ルート要素に新しい商品を追加する場合は、次のように書きます。

C#
doc.Root?.Add(
new XElement("product",
new XAttribute("id", "p004"),
new XAttribute("category", "monitor"),
new XElement("name", "ディスプレイ"),
new XElement("price", 25000),
new XElement("stock", 15)
)
);

特定の位置に要素を追加したい場合は、AddBeforeSelfAddAfterSelfを使えます。

C#
XElement? target = doc
.Descendants("product")
.FirstOrDefault(x => (string?)x.Attribute("id") == "p002");

target?.AddAfterSelf(
new XElement("product",
new XAttribute("id", "p002-1"),
new XElement("name", "マウスパッド"),
new XElement("price", 1000)
)
);

7-4. 要素や属性を削除する

要素を削除するには、Removeを使います。

C#
XElement? product = doc
.Descendants("product")
.FirstOrDefault(x => (string?)x.Attribute("id") == "p003");

product?.Remove();

属性を削除する場合もRemoveを使えます。

C#
XAttribute? category = product?.Attribute("category");
category?.Remove();

また、SetAttributeValuenullを指定して属性を削除することもできます。

C#
product?.SetAttributeValue("category", null);

複数の要素を削除する場合は、注意が必要です。LINQで取得した要素をそのまま削除すると、列挙中にコレクションが変更されて問題になることがあります。そのため、ToListで一度リスト化してから削除します。

C#
var targets = doc
.Descendants("product")
.Where(x => (int?)x.Element("stock") == 0)
.ToList();

targets.Remove();

このコードでは、在庫が0の商品をすべて削除しています。

7-5. 条件に一致した要素だけを更新する

条件に一致した要素だけを更新する場合は、Whereで絞り込んでから処理します。

C#
var accessories = doc
.Descendants("product")
.Where(x => (string?)x.Attribute("category") == "accessory");

foreach (XElement product in accessories)
{
int price = (int?)product.Element("price") ?? 0;
product.SetElementValue("price", price + 500);
}

このコードでは、categoryaccessoryの商品だけ価格を500円上げています。

在庫数が一定以下の商品にフラグを追加する処理も簡単に書けます。

C#
var lowStockProducts = doc
.Descendants("product")
.Where(x => ((int?)x.Element("stock") ?? 0) <= 10);

foreach (XElement product in lowStockProducts)
{
product.SetAttributeValue("lowStock", "true");
}

XDocumentとLINQを組み合わせることで、XMLの一部だけを効率よく更新できます。

8. XDocumentでXMLを保存する方法

XMLを作成または更新したら、必要に応じてファイルに保存します。XDocumentでは、Saveメソッドを使ってXMLファイルとして保存できます。

8-1. XMLファイルとして保存する:Save

XMLファイルとして保存する基本コードは次のとおりです。

C#
doc.Save("products.xml");

新しく作成したXMLを保存する例です。

C#
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("products",
new XElement("product",
new XAttribute("id", "p001"),
new XElement("name", "ノートPC"),
new XElement("price", 120000)
)
)
);

doc.Save("products.xml");

Saveを実行すると、指定したパスにXMLファイルが作成されます。すでに同名のファイルが存在する場合は上書きされるため注意が必要です。

8-2. 文字コードを指定して保存する

文字コードを指定して保存したい場合は、XmlWriterSettingsXmlWriterを使います。

C#
using System.Text;
using System.Xml;
using System.Xml.Linq;

XmlWriterSettings settings = new XmlWriterSettings
{
Encoding = new UTF8Encoding(false),
Indent = true
};

using XmlWriter writer = XmlWriter.Create("products.xml", settings);
doc.Save(writer);

new UTF8Encoding(false)は、BOMなしUTF-8で保存したい場合に使います。

BOM付きUTF-8で保存したい場合は、次のようにします。

C#
Encoding = new UTF8Encoding(true)

外部システムとXMLを連携する場合、文字コードの指定が重要になることがあります。保存先の仕様に合わせてエンコーディングを設定しましょう。

8-3. インデントや整形を制御する

XMLを見やすく保存したい場合は、インデントを有効にします。

C#
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true,
IndentChars = " "
};

using XmlWriter writer = XmlWriter.Create("products.xml", settings);
doc.Save(writer);

逆に、インデントなしで保存したい場合は、SaveOptions.DisableFormattingを使えます。

C#
doc.Save("products.xml", SaveOptions.DisableFormatting);

文字列として出力する場合も同様です。

C#
string xml = doc.ToString(SaveOptions.DisableFormatting);

人が読む設定ファイルなどでは整形あり、通信データやログ出力では整形なしなど、用途に応じて使い分けるとよいでしょう。

8-4. 更新後のXMLを上書き保存する際の注意点

既存のXMLファイルを読み込んで更新し、同じファイルに上書き保存する場合は注意が必要です。

C#
string path = "products.xml";

XDocument doc = XDocument.Load(path);

// XMLを更新する処理
doc.Root?.Add(
new XElement("product",
new XAttribute("id", "p004"),
new XElement("name", "ディスプレイ"),
new XElement("price", 25000)
)
);

doc.Save(path);

このように同じパスに保存すると、元のファイルは上書きされます。重要なファイルを扱う場合は、保存前にバックアップを作成するのがおすすめです。

C#
string path = "products.xml";
string backupPath = "products_backup.xml";

File.Copy(path, backupPath, overwrite: true);

XDocument doc = XDocument.Load(path);

// 更新処理
doc.Save(path);

また、複数の処理や複数のアプリケーションが同じXMLファイルを更新する場合、同時書き込みによる競合にも注意が必要です。業務システムなどで頻繁に更新されるデータは、XMLファイルではなくデータベースの利用も検討しましょう。

9. 名前空間付きXMLをXDocumentで扱う方法

XDocumentでXMLを扱うときにつまずきやすいのが、XML名前空間です。見た目は同じ要素名に見えても、名前空間が付いている場合は、単純にElement("name")Descendants("product")では取得できないことがあります。

9-1. XML名前空間とは

XML名前空間は、要素名や属性名の衝突を防ぐための仕組みです。

たとえば、次のXMLでは、products要素に名前空間が指定されています。

XML
<products xmlns="http://example.com/products">
<product id="p001">
<name>ノートPC</name>
<price>120000</price>
</product>
</products>

このXMLでは、productsproductnamepriceはすべてhttp://example.com/productsという名前空間に属しています。

そのため、次のコードでは取得できません。

C#
var products = doc.Descendants("product");

名前空間付きXMLを扱う場合は、XNamespaceを使って要素名を指定する必要があります。

9-2. XNamespaceを使って要素を取得する

名前空間付きXMLの要素を取得するには、XNamespaceを定義して、ns + "要素名"の形で指定します。

C#
XDocument doc = XDocument.Load("products.xml");

XNamespace ns = "http://example.com/products";

var products = doc.Descendants(ns + "product");

foreach (XElement product in products)
{
string? name = product.Element(ns + "name")?.Value;
Console.WriteLine(name);
}

XNamespaceを使うことで、名前空間付きの要素を正しく検索できます。

ルート要素を取得する場合も同じです。

C#
XElement? root = doc.Root;

if (root?.Name == ns + "products")
{
Console.WriteLine("products要素です。");
}

名前空間があるXMLでは、要素名だけでなく、名前空間を含めた名前で比較されることを理解しておきましょう。

9-3. 名前空間付きXMLで検索できないときの原因

名前空間付きXMLで要素が取得できない原因の多くは、名前空間を指定していないことです。

次のようなXMLがあるとします。

XML
<products xmlns="http://example.com/products">
<product>
<name>ノートPC</name>
</product>
</products>

この場合、次のコードではproductを取得できません。

C#
var products = doc.Descendants("product");

正しくは、次のように書きます。

C#
XNamespace ns = "http://example.com/products";

var products = doc.Descendants(ns + "product");

名前空間が原因かどうかを確認したい場合は、ルート要素の名前を出力してみるとわかりやすいです。

C#
Console.WriteLine(doc.Root?.Name);
Console.WriteLine(doc.Root?.Name.NamespaceName);
Console.WriteLine(doc.Root?.Name.LocalName);

NamespaceNameには名前空間URI、LocalNameには要素名が入ります。

どうしても名前空間を無視して検索したい場合は、LocalNameで比較する方法もあります。

C#
var products = doc
.Descendants()
.Where(x => x.Name.LocalName == "product");

ただし、名前空間を無視すると、異なる意味の同名要素を誤って取得する可能性があります。基本的にはXNamespaceを使って正しく指定する方法がおすすめです。

9-4. 複数の名前空間を扱う場合の書き方

複数の名前空間があるXMLでは、それぞれのXNamespaceを定義します。

XML
<root xmlns:p="http://example.com/products"
xmlns:m="http://example.com/makers">
<p:product id="p001">
<p:name>ノートPC</p:name>
<m:maker>
<m:name>Sample Maker</m:name>
</m:maker>
</p:product>
</root>

このXMLを扱うコードは次のようになります。

C#
XDocument doc = XDocument.Load("sample.xml");

XNamespace p = "http://example.com/products";
XNamespace m = "http://example.com/makers";

var products = doc.Descendants(p + "product");

foreach (XElement product in products)
{
string? productName = product.Element(p + "name")?.Value;
string? makerName = product
.Element(m + "maker")?
.Element(m + "name")?
.Value;

Console.WriteLine($"{productName} / {makerName}");
}

名前空間のプレフィックスであるpmは、XML内での表記です。C#側では、実際の名前空間URIをXNamespaceとして指定します。

プレフィックス名そのものではなく、名前空間URIが重要である点に注意しましょう。

10. XDocumentの実践サンプル:読み込み・検索・更新・保存を一連で行う

ここでは、C#のXDocumentを使って、XMLファイルを読み込み、条件に一致するデータを検索し、値を更新して保存する一連の流れを確認します。

10-1. サンプルXMLの準備

まず、次の内容でproducts.xmlを用意します。

XML
<?xml version="1.0" encoding="utf-8"?>
<products>
<product id="p001" category="pc">
<name>ノートPC</name>
<price>120000</price>
<stock>10</stock>
</product>
<product id="p002" category="accessory">
<name>マウス</name>
<price>3000</price>
<stock>50</stock>
</product>
<product id="p003" category="accessory">
<name>キーボード</name>
<price>8000</price>
<stock>20</stock>
</product>
</products>

このXMLを読み込み、idp002の商品を検索し、価格と在庫数を更新して保存します。

10-2. XMLファイルを読み込む

まずはXDocument.LoadでXMLファイルを読み込みます。

C#
XDocument doc = XDocument.Load("products.xml");

ファイルが存在しない場合やXMLの形式が不正な場合は例外が発生するため、実際のアプリケーションではtry-catchを使うと安全です。

C#
XDocument doc;

try
{
doc = XDocument.Load("products.xml");
}
catch (Exception ex)
{
Console.WriteLine("XMLの読み込みに失敗しました。");
Console.WriteLine(ex.Message);
return;
}

10-3. 条件に一致するデータを検索する

次に、id属性がp002の商品を検索します。

C#
XElement? product = doc
.Descendants("product")
.FirstOrDefault(x => (string?)x.Attribute("id") == "p002");

if (product == null)
{
Console.WriteLine("対象の商品が見つかりません。");
return;
}

属性値を条件にすることで、目的の商品を正確に取得できます。

10-4. 検索した要素の値を更新する

検索した商品について、価格と在庫数を更新します。

C#
product.SetElementValue("price", 3500);
product.SetElementValue("stock", 45);

さらに、更新日時を追加してみます。

C#
product.SetElementValue("updatedAt", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

SetElementValueを使うと、要素が存在すれば更新し、存在しなければ追加されます。

10-5. 更新結果をXMLファイルに保存する

最後に、更新したXMLを保存します。

C#
doc.Save("products.xml");

一連のコードをまとめると、次のようになります。

C#
using System;
using System.Linq;
using System.Xml.Linq;

class Program
{
static void Main()
{
string path = "products.xml";

XDocument doc;

try
{
doc = XDocument.Load(path);
}
catch (Exception ex)
{
Console.WriteLine("XMLの読み込みに失敗しました。");
Console.WriteLine(ex.Message);
return;
}

XElement? product = doc
.Descendants("product")
.FirstOrDefault(x => (string?)x.Attribute("id") == "p002");

if (product == null)
{
Console.WriteLine("対象の商品が見つかりません。");
return;
}

product.SetElementValue("price", 3500);
product.SetElementValue("stock", 45);
product.SetElementValue("updatedAt", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

try
{
doc.Save(path);
Console.WriteLine("XMLを更新して保存しました。");
}
catch (Exception ex)
{
Console.WriteLine("XMLの保存に失敗しました。");
Console.WriteLine(ex.Message);
}
}
}

このように、XDocumentを使うと、XMLの読み込み、検索、更新、保存を一連の流れでシンプルに実装できます。

11. XDocumentでよくあるエラーと対処法

XDocumentは便利ですが、XMLの構造や名前空間、nullの扱いを理解していないと、思ったように要素を取得できないことがあります。ここでは、よくあるエラーと対処法を紹介します。

11-1. Rootがnullになる原因

doc.RootはXMLのルート要素を返します。通常、正しいXMLを読み込んだ場合はルート要素が存在します。

ただし、空のXDocumentを作成した場合はRootがnullになります。

C#
XDocument doc = new XDocument();

Console.WriteLine(doc.Root == null); // True

この状態でdoc.Root.Element("product")のようにアクセスすると、NullReferenceExceptionの原因になります。

対処法として、Rootがnullでないか確認しましょう。

C#
if (doc.Root == null)
{
Console.WriteLine("ルート要素がありません。");
return;
}

また、新規作成時にはルート要素を必ず指定します。

C#
XDocument doc = new XDocument(
new XElement("products")
);

11-2. 要素が取得できない原因

Element("name")で要素が取得できない場合、主な原因は次のとおりです。

  • 要素名が間違っている

  • 大文字と小文字が一致していない

  • 想定した階層に要素が存在しない

  • 名前空間が付いている

  • ElementsDescendantsの使い分けを間違えている

たとえば、XMLが次のようになっている場合、

XML
<product>
<Name>ノートPC</Name>
</product>

C#側で次のように書くと取得できません。

C#
string? name = product.Element("name")?.Value;

XMLは大文字と小文字を区別するため、正しくは次のように書きます。

C#
string? name = product.Element("Name")?.Value;

また、直下にない要素をElementで取得しようとしているケースもよくあります。

XML
<product>
<detail>
<name>ノートPC</name>
</detail>
</product>

この場合、次のコードでは取得できません。

C#
product.Element("name");

正しくは、階層をたどります。

C#
string? name = product
.Element("detail")?
.Element("name")?
.Value;

または、階層全体から検索するならDescendantsを使います。

C#
string? name = product
.Descendants("name")
.FirstOrDefault()?
.Value;

11-3. 属性が取得できない原因

属性が取得できない場合も、要素と同じように名前の違いや大文字小文字の違いが原因になることがあります。

XML
<product ID="p001">
<name>ノートPC</name>
</product>

このXMLに対して次のコードを書くと、属性は取得できません。

C#
string? id = product.Attribute("id")?.Value;

XMLでは属性名も大文字小文字を区別します。正しくは次のように書きます。

C#
string? id = product.Attribute("ID")?.Value;

また、属性ではなく要素として定義されている場合もあります。

XML
<product>
<id>p001</id>
<name>ノートPC</name>
</product>

この場合はAttributeではなくElementを使います。

C#
string? id = product.Element("id")?.Value;

XMLの構造を確認し、対象が属性なのか要素なのかを見極めることが重要です。

11-4. 名前空間が原因で検索できない場合

名前空間付きXMLでは、要素名だけを指定しても取得できないことがあります。

XML
<products xmlns="http://example.com/products">
<product>
<name>ノートPC</name>
</product>
</products>

このXMLに対して次のコードを書いても、productは取得できません。

C#
var products = doc.Descendants("product");

対処法は、XNamespaceを使うことです。

C#
XNamespace ns = "http://example.com/products";

var products = doc.Descendants(ns + "product");

名前空間があるかどうかを確認したい場合は、次のように出力してみましょう。

C#
Console.WriteLine(doc.Root?.Name.NamespaceName);
Console.WriteLine(doc.Root?.Name.LocalName);

NamespaceNameに値が入っている場合は、名前空間付きXMLとして扱う必要があります。

11-5. XMLの形式不正による読み込みエラー

XMLの形式が正しくない場合、XDocument.LoadXDocument.ParseXmlExceptionが発生します。

たとえば、次のXMLは終了タグが不足しています。

XML
<products>
<product>
<name>ノートPC</name>
</products>

このようなXMLを読み込むと、形式不正としてエラーになります。

C#
try
{
XDocument doc = XDocument.Load("products.xml");
}
catch (XmlException ex)
{
Console.WriteLine("XMLの形式が不正です。");
Console.WriteLine(ex.Message);
}

XMLでは、開始タグと終了タグの対応、属性値の引用符、特殊文字のエスケープなどに注意が必要です。

たとえば、値の中に&をそのまま書くとエラーになります。

XML
<name>A & B</name>

正しくは、次のようにエスケープします。

XML
<name>A &amp; B</name>

外部から受け取るXMLでは形式不正の可能性があるため、読み込み時の例外処理は必ず入れておきましょう。

12. XDocumentを使うときの注意点とベストプラクティス

XDocumentはC#でXMLを扱ううえで非常に便利ですが、使い方によってはパフォーマンスや保守性の問題が出ることもあります。ここでは、実務で意識したい注意点とベストプラクティスを紹介します。

12-1. 大容量XMLを扱うときの注意点

XDocumentはXML全体をメモリ上に読み込んで扱います。そのため、大容量のXMLファイルを処理する場合は、メモリ使用量に注意が必要です。

数MBから数十MB程度のXMLであれば問題なく扱えることが多いですが、数百MB以上の大きなXMLを処理する場合、XDocumentではメモリ負荷が高くなる可能性があります。

大容量XMLを先頭から順番に読み取るだけでよい場合は、XmlReaderの利用を検討しましょう。

C#
using XmlReader reader = XmlReader.Create("large.xml");

while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "product")
{
Console.WriteLine("product要素を検出しました。");
}
}

XmlReaderはストリーム形式でXMLを読み込むため、全体をメモリに保持しません。ただし、XDocumentのようにLINQで自由に検索・編集する用途には向いていません。

XML全体を編集したい場合はXDocument、大容量XMLを順番に読むだけならXmlReaderというように使い分けるとよいでしょう。

12-2. nullチェックを忘れない

XDocumentでXMLを扱うときは、要素や属性が存在しない可能性を常に考える必要があります。

次のコードは、productまたはnameが存在しない場合に例外が発生する可能性があります。

C#
string name = doc.Root.Element("product").Element("name").Value;

安全に書くなら、null条件演算子を使います。

C#
string name = doc.Root?
.Element("product")?
.Element("name")?
.Value
?? "名称未設定";

属性も同じです。

C#
string id = product.Attribute("id")?.Value ?? "IDなし";

外部XMLや仕様変更の可能性があるXMLでは、nullチェックをしっかり行うことが重要です。

12-3. LINQで読みやすく検索処理を書く

XDocumentの検索処理は、LINQを使うと読みやすく書けます。

C#
var products = doc
.Descendants("product")
.Where(x => (string?)x.Attribute("category") == "accessory")
.Select(x => new
{
Id = (string?)x.Attribute("id"),
Name = (string?)x.Element("name"),
Price = (int?)x.Element("price") ?? 0
});

このように、XMLの要素を匿名型に変換すると、後続処理で扱いやすくなります。

さらに、条件が複雑になる場合は、変数に分けると読みやすくなります。

C#
var allProducts = doc.Descendants("product");

var targetProducts = allProducts
.Where(x => (string?)x.Attribute("category") == "accessory")
.Where(x => ((int?)x.Element("price") ?? 0) <= 5000);

1行に詰め込みすぎると読みにくくなるため、処理の意味ごとに分けるのがおすすめです。

12-4. XMLの構造が変わる場合は安全に取得する

XMLの構造が変更される可能性がある場合は、固定の階層に依存しすぎない書き方を意識しましょう。

たとえば、次のように固定階層で取得するコードは、XML構造が変わると値を取得できなくなります。

C#
string? name = doc.Root?
.Element("product")?
.Element("detail")?
.Element("name")?
.Value;

仕様上、name要素の位置が変わる可能性がある場合は、対象範囲を限定しながらDescendantsを使う方法もあります。

C#
string? name = product
.Descendants("name")
.FirstOrDefault()?
.Value;

ただし、Descendantsは階層全体を検索するため、同名要素が複数ある場合には注意が必要です。

保守性を高めるには、XML仕様を明確にし、取得処理をメソッド化しておくとよいでしょう。

C#
static string GetProductName(XElement product)
{
return product.Element("name")?.Value
?? product.Element("detail")?.Element("name")?.Value
?? "名称未設定";
}

このように取得ロジックをまとめておくと、XML構造が変わったときにも修正箇所を限定できます。

12-5. 用途に応じてXDocument・XElement・XmlReaderを使い分ける

C#でXMLを扱う方法はXDocumentだけではありません。用途に応じて適切なクラスを選ぶことが大切です。

用途おすすめ
XMLファイル全体を読み込み、検索・更新・保存したいXDocument
XMLの一部の要素だけを作成・操作したいXElement
大容量XMLを順番に読みたいXmlReader
XMLを順番に書き出したいXmlWriter
古いDOMベースのコードと連携したいXmlDocument

XDocumentは、XML全体を扱いながらLINQで検索・編集したい場合に向いています。設定ファイル、比較的小さなデータファイル、外部APIから受け取ったXMLの処理などで便利です。

一方、大容量XMLを高速に読み取るだけならXmlReaderのほうが適しています。XMLを新規に順番に書き出すだけならXmlWriterも有効です。

すべてのXML処理をXDocumentで行うのではなく、XMLのサイズや処理内容に応じて使い分けましょう。

まとめ

C#のXDocumentは、XMLの読み込み、作成、検索、更新、保存をわかりやすく扱える便利なクラスです。LINQ to XMLの仕組みにより、XMLデータをC#のオブジェクトのように操作でき、条件検索や値の取得もシンプルに書けます。

XMLファイルを読み込む場合はXDocument.Load、XML文字列を読み込む場合はXDocument.Parseを使います。要素の取得にはElementElements、階層をまたいだ検索にはDescendantsを使います。属性値はAttributeで取得でき、LINQのWhereFirstOrDefaultと組み合わせることで、条件に一致するXMLデータを効率よく検索できます。

XMLを新規作成する場合は、XDocumentXElementXAttributeを組み合わせます。更新処理では、SetElementValueSetAttributeValueを使うと、要素や属性の追加・変更を安全に行えます。保存にはSaveを使い、文字コードやインデントを制御したい場合はXmlWriterSettingsを利用します。

また、名前空間付きXMLを扱う場合は、XNamespaceを使って要素名を指定する必要があります。要素が取得できない場合は、名前空間、大文字小文字、階層構造、nullの可能性を確認しましょう。

XDocumentは非常に扱いやすい一方で、XML全体をメモリに読み込むため、大容量XMLには注意が必要です。用途に応じてXDocumentXElementXmlReaderXmlWriterを使い分けることで、C#でのXML操作をより安全で効率的に実装できます。