C#でXMLを扱う方法を基礎から解説|読み込み・作成・検索・編集の悩みを解決

はじめに

C#でXMLを扱う場面は、設定ファイルの読み込み、業務システム間のデータ連携、古いAPIレスポンスの解析、帳票データの入出力など、現在でも多くあります。

一方で、C# XMLの実装では「XmlDocumentとXDocumentのどちらを使えばよいのか」「XPathで値が取れない」「名前空間付きXMLの検索でつまずく」「保存すると文字化けする」など、初心者が迷いやすいポイントも少なくありません。

この記事では、C#でXMLを読み込み、作成、検索、編集、保存する基本から、XmlDocument・XDocument・XmlReader・XmlWriterの使い分け、XMLシリアライズ、文字化けやセキュリティ対策まで、実務で困りやすい内容を基礎から解説します。

1. C#でXMLを扱うときによくある悩みとこの記事で解決できること

1-1. XMLファイルを読み込みたいが、どのクラスを使えばよいかわからない

C#でXMLファイルを読み込む方法はいくつかあります。

代表的なものは次の4つです。

C#
XDocument
XmlDocument
XmlReader
XmlSerializer

初めてC#でXMLを扱う場合は、まず XDocument を使うのがおすすめです。LINQと相性がよく、要素の取得や検索、追加、更新を直感的に書けるためです。

一方で、既存コードが XmlDocument を使っている場合や、XPathを中心に扱いたい場合は XmlDocument が適しています。大容量XMLを先頭から順番に高速処理したい場合は XmlReader を使います。

1-2. XMLの作成・検索・編集をC#でどう書けばよいかわからない

XMLは階層構造を持つため、テキストファイルのように単純な文字列操作で編集すると壊れやすくなります。

たとえば、次のようなXMLがあるとします。

XML
<products>
<product id="1">
<name>Keyboard</name>
<price>3000</price>
</product>
</products>

C#では、要素を取得する、属性を読む、条件に合う要素を検索する、値を書き換えるといった操作をXML専用のクラスで行います。

XDocument を使えば、次のように商品名を取得できます。

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

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

Console.WriteLine(name);

このようにXML構造に沿って操作すれば、安全で読みやすいコードになります。

1-3. XmlDocument・XDocument・XmlReaderの違いがわからない

C# XMLで特に混乱しやすいのが、似たようなクラスが複数あることです。

XDocument はLINQ to XMLでXMLを扱うためのクラスです。現在のC#では、通常のXML操作に最も使いやすい選択肢です。

XmlDocument はDOM形式でXML全体をメモリに読み込んで操作する古くからあるクラスです。XPathを使った検索や、既存の.NET Framework系コードでよく使われます。

XmlReader はXMLを先頭から順番に読み進めるストリーム型のクラスです。XML全体をメモリに読み込まないため、大きなXMLファイルの処理に向いています。

1-4. 文字化け・名前空間・XPath・保存エラーでつまずく

C#でXMLを扱うときは、次のようなトラブルもよく起こります。

日本語が文字化けする
XPathでノードが取得できない
名前空間付きXMLの値が取れない
保存時にアクセス拒否エラーが出る
null参照エラーが出る
大きなXMLで処理が遅い

これらは、XMLの構造、エンコーディング、名前空間、ファイルパス、例外処理を正しく理解すれば対処できます。

この記事では、それぞれの原因と解決方法を具体的なC#コード付きで解説します。

2. C#でXMLを扱うための基礎知識

2-1. XMLとは何か

XMLとは、データを階層構造で表現するためのマークアップ言語です。

XMLは、HTMLと似たタグ形式でデータを表します。ただし、HTMLが画面表示を目的としているのに対し、XMLはデータの構造化や受け渡しを目的としています。

たとえば、商品データをXMLで表すと次のようになります。

XML
<product>
<id>1</id>
<name>Keyboard</name>
<price>3000</price>
</product>

このXMLでは、product が商品全体を表し、その中に idnameprice という要素があります。

C#では、このXMLを読み込んで、商品名だけを取得したり、価格を変更したり、新しい商品を追加したりできます。

2-2. XMLの基本構造:要素・属性・テキスト・階層

XMLを理解するうえで重要なのは、要素、属性、テキスト、階層です。

次のXMLを見てください。

XML
<product id="1" category="device">
<name>Keyboard</name>
<price>3000</price>
</product>

productnameprice は要素です。

id="1"category="device" は属性です。

Keyboard3000 はテキスト値です。

また、namepriceproduct の子要素です。このようにXMLは親子関係を持つ階層構造になっています。

C#でXMLを扱うときは、「要素の値を取るのか」「属性の値を取るのか」「子要素を検索するのか」を区別することが大切です。

2-3. C#でXMLを扱う主な方法

C#でXMLを扱う主な方法は次のとおりです。

XDocument / XElement
XmlDocument
XmlReader
XmlWriter
XmlSerializer

XDocumentXElement は、LINQ to XMLでXMLを読み書きするためのクラスです。シンプルなコードでXMLの検索、作成、編集ができます。

XmlDocument は、XML全体をDOMツリーとして扱うクラスです。XPathを使いたい場合や、古いコードとの互換性が必要な場合に使います。

XmlReader は、XMLを順番に読み込むためのクラスです。大容量XMLを効率よく処理できます。

XmlWriter は、XMLを順番に書き出すためのクラスです。大量データをXMLとして出力する場合や、細かく出力設定を制御したい場合に使います。

XmlSerializer は、XMLとC#オブジェクトを相互変換するためのクラスです。XMLをクラスとして扱いたい場合に便利です。

2-4. XmlDocument・XDocument・XmlReader・XmlWriterの使い分け

C# XMLの使い分けは、次のように考えるとわかりやすいです。

クラス特徴向いている用途
XDocumentLINQで直感的に操作できる通常のXML読み書き、検索、編集
XElementXML要素を扱う部分的なXML作成、要素追加
XmlDocumentDOM形式でXML全体を操作XPath中心の処理、既存コード保守
XmlReader前から順番に高速読み込み大容量XMLの読み込み
XmlWriter前から順番にXML出力大量データのXML出力
XmlSerializerXMLとクラスを変換オブジェクトとして扱う処理

新しくC#でXML処理を書くなら、まずは XDocument を検討するとよいでしょう。コードが読みやすく、LINQで検索しやすいため、保守性も高くなります。

3. C#でXMLファイルを読み込む方法

3-1. XDocumentでXMLを読み込む基本コード

C#でXMLファイルを読み込む最も基本的な方法は、XDocument.Load を使うことです。

たとえば、次のXMLファイルがあるとします。

XML
<?xml version="1.0" encoding="utf-8"?>
<products>
<product id="1">
<name>Keyboard</name>
<price>3000</price>
</product>
<product id="2">
<name>Mouse</name>
<price>1500</price>
</product>
</products>

C#では次のように読み込みます。

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

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

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

doc.Root はXMLのルート要素を表します。この例では products です。

要素を取得する場合は、次のように書きます。

C#
var products = doc.Root?.Elements("product");

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

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

3-2. XmlDocumentでXMLを読み込む基本コード

XmlDocument を使う場合は、次のように Load メソッドでXMLファイルを読み込みます。

C#
using System;
using System.Xml;

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

XmlNode? root = doc.DocumentElement;
Console.WriteLine(root?.Name);
}
}

特定のノードを取得するには、SelectSingleNodeSelectNodes を使います。

C#
XmlNode? nameNode = doc.SelectSingleNode("/products/product/name");

Console.WriteLine(nameNode?.InnerText);

XPathで検索したい場合は、XmlDocument が使いやすいです。

3-3. 文字列からXMLを読み込む方法

XMLファイルではなく、文字列として受け取ったXMLを読み込むこともできます。

XDocument の場合は Parse を使います。

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

string xml = @"
<product>
<name>Keyboard</name>
<price>3000</price>
</product>";

XDocument doc = XDocument.Parse(xml);

Console.WriteLine(doc.Root?.Element("name")?.Value);

XmlDocument の場合は LoadXml を使います。

C#
using System;
using System.Xml;

string xml = @"
<product>
<name>Keyboard</name>
<price>3000</price>
</product>";

XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);

Console.WriteLine(doc.SelectSingleNode("/product/name")?.InnerText);

APIレスポンスやデータベースから取得したXML文字列を処理する場合によく使います。

3-4. 大きなXMLをXmlReaderで高速に読み込む方法

大きなXMLファイルを扱う場合、XDocumentXmlDocument はXML全体をメモリに読み込むため、メモリ使用量が増えることがあります。

そのような場合は XmlReader を使います。

C#
using System;
using System.Xml;

class Program
{
static void Main()
{
using XmlReader reader = XmlReader.Create("products.xml");

while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "name")
{
string name = reader.ReadElementContentAsString();
Console.WriteLine(name);
}
}
}
}

XmlReader は前から順番にXMLを読み込むため、全体をメモリに保持しません。数百MB以上のXMLファイルや大量データを処理するときに有効です。

ただし、親要素に戻ったり、自由に検索したりする処理は苦手です。小〜中規模のXMLであれば、XDocument のほうが書きやすいです。

3-5. XML読み込み時によくあるエラーと対処法

XML読み込み時によくあるエラーには、次のようなものがあります。

ファイルが存在しない
XMLの形式が壊れている
文字コードが合っていない
アクセス権限がない
名前空間の影響で要素が取得できない

ファイルが存在するか確認するには、File.Exists を使います。

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

string path = "products.xml";

if (!File.Exists(path))
{
Console.WriteLine("XMLファイルが見つかりません。");
return;
}

XDocument doc = XDocument.Load(path);

XMLの形式が壊れている場合は、XmlException が発生します。

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

try
{
XDocument doc = XDocument.Load("products.xml");
}
catch (XmlException ex)
{
Console.WriteLine("XMLの形式が正しくありません。");
Console.WriteLine(ex.Message);
}
catch (IOException ex)
{
Console.WriteLine("ファイルの読み込みに失敗しました。");
Console.WriteLine(ex.Message);
}

実務では、XMLファイルの読み込み処理には例外処理を入れておくことが重要です。

4. C#でXMLの値を取得・検索する方法

4-1. 要素の値を取得する方法

XDocument で要素の値を取得する基本は、ElementValue です。

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

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

Console.WriteLine(name);

複数の要素を取得する場合は、Elements を使います。

C#
foreach (var product in doc.Root!.Elements("product"))
{
string? name = product.Element("name")?.Value;
string? price = product.Element("price")?.Value;

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

Element は最初に見つかった1つの子要素を取得します。Elements は条件に一致する複数の子要素を取得します。

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

XMLの属性を取得するには、Attribute を使います。

XML
<product id="1" category="device">
<name>Keyboard</name>
<price>3000</price>
</product>

C#では次のように属性値を取得します。

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

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

Console.WriteLine(id);
Console.WriteLine(category);

属性が存在しない可能性がある場合は、?. を使ってnull参照エラーを防ぎます。

数値として扱いたい場合は、変換処理を入れます。

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

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

4-3. LINQ to XMLで条件に合う要素を検索する方法

XDocument の大きなメリットは、LINQでXMLを検索できることです。

たとえば、価格が2000円以上の商品だけを取得する場合は、次のように書けます。

C#
var expensiveProducts = doc.Root!
.Elements("product")
.Where(p => int.Parse(p.Element("price")!.Value) >= 2000);

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

ただし、実務では値が空だったり不正だったりする可能性があります。その場合は TryParse を使うと安全です。

C#
var products = doc.Root!
.Elements("product")
.Where(p =>
{
string? priceText = p.Element("price")?.Value;
return int.TryParse(priceText, out int price) && price >= 2000;
});

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

属性で検索することもできます。

C#
var target = doc.Root!
.Elements("product")
.FirstOrDefault(p => (string?)p.Attribute("id") == "2");

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

LINQ to XMLを使うと、条件検索、並び替え、集計などをC#らしく書けます。

4-4. XPathでXMLノードを検索する方法

XPathは、XMLの場所をパス形式で指定してノードを検索する方法です。

XDocument でもXPathを使えますが、追加の名前空間が必要です。

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

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

var name = doc.XPathSelectElement("/products/product/name");

Console.WriteLine(name?.Value);

複数ノードを取得する場合は、XPathSelectElements を使います。

C#
var names = doc.XPathSelectElements("/products/product/name");

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

XmlDocument の場合は、次のように書きます。

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

XmlNodeList? nodes = doc.SelectNodes("/products/product/name");

foreach (XmlNode node in nodes!)
{
Console.WriteLine(node.InnerText);
}

XPathは、XML構造が複雑な場合や、既存システムでXPath式が使われている場合に便利です。

4-5. 名前空間付きXMLを検索する方法

C# XMLで値が取得できない原因として特に多いのが、XML名前空間です。

たとえば、次のXMLを見てください。

XML
<products xmlns="http://example.com/products">
<product id="1">
<name>Keyboard</name>
</product>
</products>

見た目は productsproductname ですが、実際には名前空間付きの要素です。そのため、単純に Element("product") と書いても取得できません。

XDocument では XNamespace を使います。

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

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

var name = doc.Root?
.Element(ns + "product")?
.Element(ns + "name")?
.Value;

Console.WriteLine(name);

XPathで名前空間付きXMLを検索する場合は、XmlNamespaceManager を使います。

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

XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable);
manager.AddNamespace("p", "http://example.com/products");

XmlNode? node = doc.SelectSingleNode("/p:products/p:product/p:name", manager);

Console.WriteLine(node?.InnerText);

デフォルト名前空間があるXMLでは、XPathにもプレフィックスを付けて検索する必要があります。

4-6. 値が取得できないときの確認ポイント

C#でXMLの値が取得できないときは、次の点を確認します。

要素名の大文字・小文字が一致しているか
階層が正しいか
属性と要素を混同していないか
名前空間が付いていないか
対象ノードが複数階層下にないか
XMLファイルの読み込み自体に成功しているか

XMLの要素名は大文字と小文字を区別します。

XML
<Name>Keyboard</Name>

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

C#
product.Element("name")

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

C#
product.Element("Name")

また、属性を要素として取得しようとしても値は取れません。

XML
<product id="1" />

この場合は Element("id") ではなく、Attribute("id") を使います。

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

値が取れないときは、まずXMLの実際の構造を確認することが大切です。

5. C#でXMLを作成する方法

5-1. XDocumentで新しいXMLを作成する方法

C#で新しいXMLを作成する場合、XDocumentXElement を使うと簡単です。

C#
using System.Xml.Linq;

XDocument doc = new XDocument(
new XElement("products",
new XElement("product",
new XAttribute("id", "1"),
new XElement("name", "Keyboard"),
new XElement("price", 3000)
),
new XElement("product",
new XAttribute("id", "2"),
new XElement("name", "Mouse"),
new XElement("price", 1500)
)
)
);

doc.Save("products.xml");

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

XML
<products>
<product id="1">
<name>Keyboard</name>
<price>3000</price>
</product>
<product id="2">
<name>Mouse</name>
<price>1500</price>
</product>
</products>

XElement はXMLの要素、XAttribute はXMLの属性を表します。

5-2. XElementで要素や属性を追加する方法

既存のXMLに要素を追加する場合は、Add を使います。

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

doc.Root?.Add(
new XElement("product",
new XAttribute("id", "3"),
new XElement("name", "Monitor"),
new XElement("price", 20000)
)
);

doc.Save("products.xml");

属性だけを追加することもできます。

C#
var product = doc.Root?
.Elements("product")
.FirstOrDefault(p => (string?)p.Attribute("id") == "1");

product?.SetAttributeValue("stock", "10");

doc.Save("products.xml");

SetAttributeValue は、属性が存在すれば更新し、存在しなければ追加します。

5-3. XmlWriterでXMLを出力する方法

XmlWriter を使うと、XMLを順番に書き出せます。

C#
using System.Xml;

XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true
};

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

writer.WriteStartDocument();
writer.WriteStartElement("products");

writer.WriteStartElement("product");
writer.WriteAttributeString("id", "1");
writer.WriteElementString("name", "Keyboard");
writer.WriteElementString("price", "3000");
writer.WriteEndElement();

writer.WriteStartElement("product");
writer.WriteAttributeString("id", "2");
writer.WriteElementString("name", "Mouse");
writer.WriteElementString("price", "1500");
writer.WriteEndElement();

writer.WriteEndElement();
writer.WriteEndDocument();

XmlWriter は、データベースから大量のレコードを読みながらXMLを書き出すようなケースに向いています。

ただし、手軽に小さなXMLを作るだけなら、XDocument のほうが読みやすいです。

5-4. インデント付きで読みやすく保存する方法

XDocument.Save を使うと、通常は読みやすいインデント付きで保存されます。

より細かく制御したい場合は、XmlWriterSettings を使います。

C#
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
NewLineChars = Environment.NewLine
};

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

インデント付きで保存すると、人間がXMLを確認しやすくなります。

設定ファイルやデバッグ用のXMLではインデントありが便利です。一方、大量データでファイルサイズを少しでも小さくしたい場合は、インデントなしで保存することもあります。

5-5. エンコーディングを指定してXMLを作成する方法

日本語を含むXMLでは、エンコーディングを意識することが重要です。

UTF-8で保存したい場合は、XmlWriterSettings.Encoding を指定します。

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

XDocument doc = new XDocument(
new XElement("message", "こんにちは")
);

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

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

new UTF8Encoding(false) は、BOMなしUTF-8を指定しています。

XML宣言も出力したい場合は、次のようにします。

C#
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("message", "こんにちは")
);

XMLを外部システムと連携する場合は、相手側が期待する文字コードに合わせる必要があります。

6. C#でXMLを編集・更新・削除する方法

6-1. 既存XMLの要素を更新する方法

既存XMLの要素を更新するには、対象要素を検索して Value を変更します。

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

var product = doc.Root?
.Elements("product")
.FirstOrDefault(p => (string?)p.Attribute("id") == "1");

var priceElement = product?.Element("price");

if (priceElement != null)
{
priceElement.Value = "3500";
}

doc.Save("products.xml");

このコードでは、id="1" の商品の価格を 3500 に変更しています。

SetElementValue を使う方法もあります。

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

SetElementValue は、要素が存在すれば更新し、存在しなければ追加します。

6-2. 属性の値を変更する方法

属性の値を変更するには、SetAttributeValue を使います。

C#
var product = doc.Root?
.Elements("product")
.FirstOrDefault(p => (string?)p.Attribute("id") == "1");

product?.SetAttributeValue("category", "device");

doc.Save("products.xml");

既存の属性を直接変更することもできます。

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

if (attribute != null)
{
attribute.Value = "accessory";
}

属性が存在するかどうかわからない場合は、SetAttributeValue のほうが便利です。

6-3. 新しい要素を追加する方法

新しい要素を追加する場合は、親要素に対して Add を使います。

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

var product = new XElement("product",
new XAttribute("id", "4"),
new XElement("name", "USB Cable"),
new XElement("price", 800)
);

doc.Root?.Add(product);

doc.Save("products.xml");

特定の要素の中に子要素を追加することもできます。

C#
var target = doc.Root?
.Elements("product")
.FirstOrDefault(p => (string?)p.Attribute("id") == "1");

target?.Add(new XElement("stock", 20));

doc.Save("products.xml");

要素を追加するときは、XMLの構造が一貫しているか確認しましょう。商品データであれば、すべての商品に同じ種類の子要素があるほうが扱いやすくなります。

6-4. 不要な要素や属性を削除する方法

要素を削除するには、対象の XElement に対して Remove を使います。

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

var target = doc.Root?
.Elements("product")
.FirstOrDefault(p => (string?)p.Attribute("id") == "2");

target?.Remove();

doc.Save("products.xml");

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

C#
var product = doc.Root?
.Elements("product")
.FirstOrDefault(p => (string?)p.Attribute("id") == "1");

product?.Attribute("category")?.Remove();

doc.Save("products.xml");

条件に一致する複数の要素を削除する場合は、一度 ToList に変換してから削除します。

C#
var targets = doc.Root!
.Elements("product")
.Where(p =>
{
string? priceText = p.Element("price")?.Value;
return int.TryParse(priceText, out int price) && price < 1000;
})
.ToList();

foreach (var target in targets)
{
target.Remove();
}

doc.Save("products.xml");

ToList を付けずに列挙中の要素を削除すると、処理が不安定になることがあるため注意しましょう。

6-5. 編集したXMLをファイルに保存する方法

編集したXMLを保存するには、Save を使います。

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

別名で保存する場合は、別のファイルパスを指定します。

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

保存時に文字コードやインデントを指定したい場合は、XmlWriter を使います。

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

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

保存時にエラーが出る場合は、ファイルが他のアプリで開かれていないか、保存先フォルダに書き込み権限があるかを確認します。

7. C#でXMLをオブジェクトに変換する方法

7-1. XMLシリアライズとは何か

XMLシリアライズとは、C#のオブジェクトをXMLに変換したり、XMLをC#のオブジェクトに変換したりする仕組みです。

たとえば、次のXMLがあるとします。

XML
<Product>
<Id>1</Id>
<Name>Keyboard</Name>
<Price>3000</Price>
</Product>

このXMLを次のようなC#クラスに変換できます。

C#
public class Product
{
public int Id { get; set; }
public string? Name { get; set; }
public int Price { get; set; }
}

XMLを直接 XDocument で操作するのではなく、C#クラスとして扱いたい場合に XmlSerializer を使います。

7-2. XMLをC#クラスにデシリアライズする方法

XMLをC#オブジェクトに変換することをデシリアライズといいます。

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

public class Product
{
public int Id { get; set; }
public string? Name { get; set; }
public int Price { get; set; }
}

class Program
{
static void Main()
{
XmlSerializer serializer = new XmlSerializer(typeof(Product));

using FileStream stream = new FileStream("product.xml", FileMode.Open);

Product? product = serializer.Deserialize(stream) as Product;

Console.WriteLine(product?.Name);
}
}

XMLの要素名とC#クラスのプロパティ名が一致していれば、基本的には自動でマッピングされます。

7-3. C#オブジェクトをXMLにシリアライズする方法

C#オブジェクトをXMLに変換することをシリアライズといいます。

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

Product product = new Product
{
Id = 1,
Name = "Keyboard",
Price = 3000
};

XmlSerializer serializer = new XmlSerializer(typeof(Product));

using FileStream stream = new FileStream("product.xml", FileMode.Create);

serializer.Serialize(stream, product);

出力されるXMLには、既定で名前空間やXML宣言が含まれることがあります。

不要な名前空間を抑えたい場合は、次のようにします。

C#
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "");

using FileStream stream = new FileStream("product.xml", FileMode.Create);
serializer.Serialize(stream, product, namespaces);

7-4. Listや配列を含むXMLを扱う方法

商品一覧のように複数データを含むXMLでは、Listを持つクラスを作ります。

XML
<ProductList>
<Products>
<Product>
<Id>1</Id>
<Name>Keyboard</Name>
<Price>3000</Price>
</Product>
<Product>
<Id>2</Id>
<Name>Mouse</Name>
<Price>1500</Price>
</Product>
</Products>
</ProductList>

対応するC#クラスは次のようになります。

C#
using System.Collections.Generic;
using System.Xml.Serialization;

public class ProductList
{
public List<Product> Products { get; set; } = new();
}

public class Product
{
public int Id { get; set; }
public string? Name { get; set; }
public int Price { get; set; }
}

デシリアライズは次のように行います。

C#
XmlSerializer serializer = new XmlSerializer(typeof(ProductList));

using FileStream stream = new FileStream("products.xml", FileMode.Open);

ProductList? productList = serializer.Deserialize(stream) as ProductList;

foreach (var product in productList?.Products ?? new List<Product>())
{
Console.WriteLine($"{product.Name}: {product.Price}");
}

XMLの要素名とクラス名が一致しない場合は、属性で対応できます。

C#
[XmlRoot("ProductList")]
public class ProductList
{
[XmlArray("Products")]
[XmlArrayItem("Product")]
public List<Product> Products { get; set; } = new();
}

7-5. シリアライズで失敗しやすいポイント

XMLシリアライズで失敗しやすいポイントは次のとおりです。

クラスに引数なしコンストラクタがない
プロパティがpublicではない
XMLの要素名とプロパティ名が合っていない
名前空間が一致していない
Listや配列の構造がXMLと合っていない
読み込むXMLのルート要素がクラスと合っていない

XmlSerializer は、基本的にpublicなプロパティを対象にします。

C#
public string Name { get; set; }

次のようにprivate setterやフィールド中心の設計にしていると、思ったようにシリアライズされないことがあります。

C#
public string Name { get; private set; }

XML要素名とC#のプロパティ名が違う場合は、XmlElement を使います。

C#
public class Product
{
[XmlElement("product_name")]
public string? Name { get; set; }
}

外部システムのXMLを扱う場合は、XML構造に合わせてクラス側の属性を調整することが大切です。

8. C#でXMLを扱う実践サンプル

8-1. 設定ファイルXMLを読み込むサンプル

アプリケーション設定をXMLで管理する例です。

XML
<settings>
<database>
<host>localhost</host>
<port>5432</port>
</database>
<log>
<level>Info</level>
</log>
</settings>

C#で読み込むコードは次のとおりです。

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

string? host = doc.Root?
.Element("database")?
.Element("host")?
.Value;

string? portText = doc.Root?
.Element("database")?
.Element("port")?
.Value;

string? logLevel = doc.Root?
.Element("log")?
.Element("level")?
.Value;

int port = int.TryParse(portText, out int result) ? result : 0;

Console.WriteLine($"Host: {host}");
Console.WriteLine($"Port: {port}");
Console.WriteLine($"LogLevel: {logLevel}");

設定ファイルでは、値が存在しない場合も想定してnullチェックや既定値を用意しておくと安全です。

8-2. 商品一覧XMLを検索するサンプル

商品一覧XMLから、指定価格以上の商品を検索するサンプルです。

XML
<products>
<product id="1">
<name>Keyboard</name>
<price>3000</price>
</product>
<product id="2">
<name>Mouse</name>
<price>1500</price>
</product>
<product id="3">
<name>Monitor</name>
<price>20000</price>
</product>
</products>

C#コードは次のとおりです。

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

var results = doc.Root!
.Elements("product")
.Where(p =>
{
string? priceText = p.Element("price")?.Value;
return int.TryParse(priceText, out int price) && price >= 3000;
});

foreach (var product in results)
{
string? id = product.Attribute("id")?.Value;
string? name = product.Element("name")?.Value;
string? price = product.Element("price")?.Value;

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

LINQ to XMLを使うと、XMLの条件検索を簡潔に書けます。

8-3. XMLデータを更新して保存するサンプル

指定した商品の価格を更新して保存するサンプルです。

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

string targetId = "2";

var product = doc.Root?
.Elements("product")
.FirstOrDefault(p => (string?)p.Attribute("id") == targetId);

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

product.SetElementValue("price", "1800");

doc.Save("products.xml");

Console.WriteLine("XMLを更新しました。");

更新処理では、対象データが見つからない可能性を必ず考慮しましょう。

8-4. APIレスポンスのXMLを解析するサンプル

APIからXML文字列を受け取った場合は、XDocument.Parse で解析できます。

C#
string responseXml = @"
<response>
<status>success</status>
<user>
<id>100</id>
<name>Taro</name>
</user>
</response>";

XDocument doc = XDocument.Parse(responseXml);

string? status = doc.Root?.Element("status")?.Value;
string? userId = doc.Root?.Element("user")?.Element("id")?.Value;
string? userName = doc.Root?.Element("user")?.Element("name")?.Value;

Console.WriteLine($"Status: {status}");
Console.WriteLine($"UserId: {userId}");
Console.WriteLine($"UserName: {userName}");

APIレスポンスでは、エラー時だけXML構造が変わることもあります。そのため、要素が存在しない場合に備えてnullチェックを入れることが重要です。

8-5. CSVやJSONとの使い分け

XML、CSV、JSONは、それぞれ向いている用途が異なります。

XMLは、階層構造、属性、名前空間、スキーマ検証が必要なデータに向いています。業務システム間連携や、古いWebサービス、帳票関連データなどでよく使われます。

CSVは、表形式の単純なデータに向いています。行と列で表せるデータならCSVのほうが軽量です。

JSONは、Web APIやJavaScriptとの相性がよく、現在のWeb開発では広く使われています。

C#で設定ファイルを扱うだけならJSONのほうが簡単な場合もあります。ただし、既存システムがXMLを使っている場合や、厳密なデータ構造を表したい場合はXMLが有効です。

9. C#でXMLを扱うときの注意点

9-1. 文字化けを防ぐエンコーディング設定

XMLの文字化けを防ぐには、読み込み側と保存側の文字コードを一致させることが重要です。

XML宣言には、文字コードを指定できます。

XML
<?xml version="1.0" encoding="utf-8"?>

C#でUTF-8として保存する場合は、次のようにします。

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

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

日本語を含むXMLでは、基本的にUTF-8を使うと扱いやすいです。

ただし、外部システムがShift_JISを要求する場合は、相手側仕様に合わせる必要があります。

9-2. null参照エラーを防ぐ書き方

XMLは、必ず期待どおりの構造になっているとは限りません。

次のようなコードは、要素が存在しない場合にエラーになります。

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

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

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

必須項目として扱いたい場合は、明示的にチェックします。

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

if (nameElement == null)
{
throw new InvalidOperationException("name要素が存在しません。");
}

string name = nameElement.Value;

任意項目か必須項目かによって、null時の扱いを分けることが大切です。

9-3. 大容量XMLでパフォーマンスを落とさない方法

大容量XMLを XDocumentXmlDocument で読み込むと、XML全体がメモリに展開されます。

数MB程度であれば問題ないことが多いですが、数百MB以上のXMLではメモリ使用量や処理時間が問題になることがあります。

大容量XMLでは、XmlReader を使って必要な部分だけを読み取るのが有効です。

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

while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "product")
{
string? id = reader.GetAttribute("id");
Console.WriteLine(id);
}
}

また、すべての要素をリスト化してから処理するのではなく、読みながら処理することでメモリ使用量を抑えられます。

9-4. XML外部実体参照のセキュリティ対策

外部から受け取ったXMLを処理する場合は、XML外部実体参照、いわゆるXXEに注意が必要です。

安全性を高めるためには、XmlReaderSettings でDTD処理を禁止します。

C#
XmlReaderSettings settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null
};

using XmlReader reader = XmlReader.Create("input.xml", settings);
XDocument doc = XDocument.Load(reader);

信頼できないXMLをそのまま読み込むと、意図しない外部ファイル参照やサービス妨害につながる可能性があります。

外部入力のXMLを扱う場合は、DTDを禁止し、不要な外部リソース解決を無効にすることが重要です。

9-5. XMLスキーマで入力データを検証する方法

XMLの構造を検証したい場合は、XML Schema、つまりXSDを使います。

C#では、XmlReaderSettings にスキーマを設定して検証できます。

C#
using System;
using System.Xml;
using System.Xml.Schema;

XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas.Add(null, "products.xsd");
settings.ValidationType = ValidationType.Schema;

settings.ValidationEventHandler += (sender, e) =>
{
Console.WriteLine($"検証エラー: {e.Message}");
};

using XmlReader reader = XmlReader.Create("products.xml", settings);

while (reader.Read())
{
}

XMLスキーマを使うと、必須要素、データ型、出現回数などを検証できます。

外部システムから受け取るXMLや、形式の正確性が重要な業務データでは、スキーマ検証を検討するとよいでしょう。

10. C# XMLに関するよくある質問

10-1. C#でXMLを扱うならXmlDocumentとXDocumentのどちらがよいか

新しくC#でXML処理を書くなら、基本的には XDocument がおすすめです。

XDocument はLINQ to XMLに対応しており、要素の検索、追加、更新、削除を読みやすく書けます。

C#
var product = doc.Root!
.Elements("product")
.FirstOrDefault(p => (string?)p.Attribute("id") == "1");

一方、既存システムが XmlDocument を使っている場合や、XPath中心のコードが多い場合は、XmlDocument を使い続けるほうが自然なこともあります。

迷った場合は、通常のXML操作なら XDocument、XPath中心や既存コード保守なら XmlDocument と考えるとよいでしょう。

10-2. LINQ to XMLとXPathはどちらを使うべきか

C#らしく書きたい場合は、LINQ to XMLがおすすめです。

C#
var results = doc.Root!
.Elements("product")
.Where(p => (string?)p.Element("name") == "Keyboard");

XMLの階層パスを文字列で指定したい場合や、既存のXPath式がある場合はXPathが便利です。

C#
var node = doc.XPathSelectElement("/products/product[name='Keyboard']");

LINQ to XMLは型安全でリファクタリングしやすく、C#コードとして読みやすいのがメリットです。

XPathはXMLの構造検索を短く表現できる一方、文字列ベースなのでパスの間違いに気づきにくいことがあります。

10-3. XMLの名前空間があると値を取得できないのはなぜか

XMLに名前空間がある場合、要素名は単なる product ではなく、名前空間URIを含んだ名前として扱われます。

たとえば、次のXMLでは、product は名前空間 http://example.com/products に属しています。

XML
<products xmlns="http://example.com/products">
<product>
<name>Keyboard</name>
</product>
</products>

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

C#
doc.Root?.Element("product")

正しくは XNamespace を使います。

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

var name = doc.Root?
.Element(ns + "product")?
.Element(ns + "name")?
.Value;

名前空間付きXMLで値が取得できない場合は、まずXML宣言やルート要素の xmlns を確認しましょう。

10-4. XMLファイルを上書き保存する方法は何か

XMLファイルを上書き保存するには、読み込んだファイルと同じパスに Save します。

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

doc.Root?.Add(
new XElement("product",
new XAttribute("id", "3"),
new XElement("name", "Monitor"),
new XElement("price", 20000)
)
);

doc.Save("products.xml");

上書き保存前にバックアップを取りたい場合は、別ファイルにコピーしておくと安全です。

C#
File.Copy("products.xml", "products_backup.xml", overwrite: true);
doc.Save("products.xml");

保存時にエラーが出る場合は、ファイルが開かれていないか、書き込み権限があるか、パスが正しいかを確認してください。

10-5. C#でXMLとJSONはどちらを使うべきか

新しいWeb APIや設定ファイルでは、JSONが使われることが多くなっています。JSONは軽量で、JavaScriptやWebサービスとの相性がよいからです。

一方で、XMLには次のような強みがあります。

属性を使える
名前空間を使える
スキーマ検証ができる
階層構造を厳密に表現しやすい
既存の業務システムで広く使われている

C#で新規開発をする場合、Web APIや軽量な設定ファイルならJSONが扱いやすいです。

しかし、既存システム連携、帳票、標準規格、XMLスキーマが必要なデータではXMLを使う価値があります。

どちらが優れているというより、データの用途や連携先の仕様に合わせて選ぶことが重要です。

まとめ

C#でXMLを扱うには、目的に応じて適切なクラスを選ぶことが大切です。

通常のXML読み込み、検索、作成、編集には XDocumentXElement が使いやすく、LINQ to XMLによって直感的なコードを書けます。

XPathを中心に扱いたい場合や、古い既存コードを保守する場合は XmlDocument が適しています。

大容量XMLを効率よく読み込む場合は XmlReader、大量データを順番に出力する場合は XmlWriter が有効です。

また、XMLをC#クラスとして扱いたい場合は XmlSerializer を使うことで、XMLとオブジェクトを相互変換できます。

C# XMLでつまずきやすいポイントは、名前空間、エンコーディング、null参照、XPath、保存時の権限、大容量データの処理です。特に名前空間付きXMLでは、見た目の要素名だけでなく XNamespaceXmlNamespaceManager を正しく使う必要があります。

まずは XDocument.Load でXMLを読み込み、ElementAttributeElements、LINQ検索、Save に慣れるところから始めると、C#でXMLを扱う基本が身につきます。XMLの構造を正しく理解し、目的に合った方法を選べば、読み込み、作成、検索、編集、保存まで安定して実装できます。