XML 在 .NET 生態系統中扮演重要角色:設定檔 (app.config/web.config)、資料交換、文件格式 (Office Open XML)、WS-* 訊息。本文以經典 XmlDocument (DOM) 為基礎,示範完整 CRUD 操作,並對比 XPath、LINQ to XML (XDocument) 現代化寫法,幫助你選擇最適合的 API。
bookstore.xml<?xml version="1.0" encoding="utf-8"?>
<bookstore>
<book genre="fantasy" ISBN="2-3631-4">
<title>Oberon's Legacy</title>
<author>Corets, Eva</author>
<price>5.95</price>
</book>
</bookstore>
var doc = new XmlDocument();
doc.Load("bookstore.xml"); // 或 LoadXml(string), Load(Stream)
// 根節點
XmlNode root = doc.DocumentElement; // 或 SelectSingleNode("bookstore")
// 取得所有 book 節點
XmlNodeList books = root.SelectNodes("book"); // XPath
// 或 root.ChildNodes (含文字節點、註解)
// 建立元素與屬性
XmlElement newBook = doc.CreateElement("book");
newBook.SetAttribute("genre", "scifi");
newBook.SetAttribute("ISBN", "978-3-16-148410-0");
// 子元素
XmlElement title = doc.CreateElement("title");
title.InnerText = "Dune";
newBook.AppendChild(title);
XmlElement author = doc.CreateElement("author");
author.InnerText = "Frank Herbert";
newBook.AppendChild(author);
XmlElement price = doc.CreateElement("price");
price.InnerText = "12.99";
newBook.AppendChild(price);
// 加入根節點
root.AppendChild(newBook);
doc.Save("bookstore.xml");
// 方式 A: XPath 定位 + 直接修改
XmlNode book = root.SelectSingleNode("book[@genre='fantasy']");
if (book != null)
{
XmlElement elem = (XmlElement)book;
elem.SetAttribute("genre", "updated-fantasy"); // 修改屬性
XmlNode authorNode = elem.SelectSingleNode("author");
if (authorNode != null) authorNode.InnerText = "New Author"; // 修改文字
}
// 方式 B: 遍歷 ChildNodes
foreach (XmlNode node in root.ChildNodes)
{
if (node is XmlElement el && el.GetAttribute("genre") == "fantasy")
{
el.SetAttribute("genre", "updated-fantasy");
break;
}
}
doc.Save("bookstore.xml");
// 刪除屬性
XmlNode book = root.SelectSingleNode("book[@genre='fantasy']");
((XmlElement)book).RemoveAttribute("genre");
// 刪除整個節點 (含子樹)
XmlNode bookToRemove = root.SelectSingleNode("book[@genre='updated-fantasy']");
if (bookToRemove != null) root.RemoveChild(bookToRemove);
// 刪除節點內容但保留元素
bookToRemove.RemoveAll(); // 變成
doc.Save("bookstore.xml");
foreach (XmlNode book in root.SelectNodes("book"))
{
XmlElement el = (XmlElement)book;
Console.WriteLine($"Genre: {el.GetAttribute("genre")}, ISBN: {el.GetAttribute("ISBN")}");
foreach (XmlNode child in el.ChildNodes)
{
if (child.NodeType == XmlNodeType.Element)
Console.WriteLine($" {child.Name}: {child.InnerText}");
}
}
| 需求 | XPath | 方法 |
|---|---|---|
| 所有 book | //book 或 /bookstore/book | SelectNodes |
| genre="fantasy" 的 book | //book[@genre='fantasy'] | SelectSingleNode |
| 第 2 本 book | //book[2] | |
| 所有 title 文字 | //title/text() | SelectNodes 回傳 XmlNodeList |
| 包含 "Legacy" 的 title | //title[contains(text(),'Legacy')] | |
| price > 10 的 book | //book[price > 10] |
注意:XmlDocument 的 XPath 支援 XPath 1.0,不支援 2.0/3.0 功能 (如 matches())。
XDocument) 現代化寫法 (.NET 3.5+)優點:函數式建構、Lambda 查詢、命名空間處理簡單、可序列化為字串/Stream、效能優於 DOM。
XDocument doc = XDocument.Load("bookstore.xml");
// 所有 book
var books = doc.Descendants("book");
// 篩選 genre
var fantasyBooks = doc.Descendants("book")
.Where(b => (string)b.Attribute("genre") == "fantasy");
// 投影匿名型別
var list = doc.Descendants("book")
.Select(b => new
{
Genre = (string)b.Attribute("genre"),
ISBN = (string)b.Attribute("ISBN"),
Title = (string)b.Element("title"),
Author = (string)b.Element("author"),
Price = (decimal)b.Element("price")
})
.ToList();
XElement newBook = new XElement("book",
new XAttribute("genre", "mystery"),
new XAttribute("ISBN", "978-0-307-47427-8"),
new XElement("title", "The Girl with the Dragon Tattoo"),
new XElement("author", "Stieg Larsson"),
new XElement("price", 9.99)
);
doc.Root.Add(newBook);
doc.Save("bookstore.xml");
var book = doc.Descendants("book")
.FirstOrDefault(b => (string)b.Attribute("genre") == "fantasy");
if (book != null)
{
book.SetAttributeValue("genre", "updated-fantasy");
book.Element("author").SetValue("New Author");
}
doc.Save("bookstore.xml");
// 刪除屬性
book.Attribute("genre").Remove();
// 刪除整個元素
book.Remove(); // 含子樹
// 刪除子內容
book.Elements().Remove(); // 保留 book 元素與屬性
doc.Save("bookstore.xml");
// XmlDocument: 需 XmlNamespaceManager
var nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("ns", "https://example.com/ns");
var nodes = doc.SelectNodes("//ns:book", nsmgr);
// LINQ to XML: XNamespace 隱性轉換
XNamespace ns = "https://example.com/ns";
var books = doc.Descendants(ns + "book"); // 簡潔!
| API | 模型 | 記憶體 | 適合場景 |
|---|---|---|---|
XmlDocument | DOM (樹狀完整載入) | 高 (整份文件在記憶體) | 小型文件、頻繁隨機存取、需 XPath 1.0 |
XDocument | DOM (函數式) | 高 | 中小型文件、LINQ 查詢、建構輸出 |
XmlReader | 前向唯讀流 | 極低 (逐節點) | 大型 XML (GB 級)、單向讀取、高效能 |
XmlWriter | 前向寫入流 | 極低 | 大量產生 XML、串流輸出 |
若資料模型為類別,優先考慮:
System.Text.Json (內建、高效能、UTF-8)XmlSerializer (屬性控制 XML 形狀、舊系統相容)DataContractSerializer (WCF 相容、序列化圖形)手工 XML 操作僅用於:無結構化文件處理、舊版相容、特定 XML 方言 (XSLT、SVG、XAML)。
XDocument (LINQ to XML):語法簡潔、型別安全、易維護。XmlDocument。XmlReader/XmlWriter。System.Text.Json / XmlSerializer。XDocument 明顯優勢。從 XmlDocument 到 XDocument,再到 XmlReader/JsonSerializer,.NET 的 XML 生態已演進為完整工具箱。理解各 API 的記憶體模型、查詢能力、建構風格,才能在效能、可維護性、相容性三角中做出正確選擇。建議新專案預設採用 XDocument + System.Text.Json 雙軌並行,僅在必要時落回底層流式 API。