CodeGym /課程 /C# SELF /LINQ to XML: XDocument

LINQ to XML: XDocument, XElement

C# SELF
等級 48 , 課堂 3
開放

1. 介紹

LINQ 的出現讓在 .NET 中處理 XML 發生了革命。如果以前要修改 XML 得寫冗長的結構,用到 NodeListXmlElement,現在我們可以用 LINQ 風格來表達所有操作。這有點像你從坎坷的碎石路換到高速公路,一切都變得順暢。

LINQ to XML 解決三個主要問題:

  • 簡化建立與讀取 XML 的語法。
  • 讓文件導航像操作集合一樣直觀。
  • 允許把 XML 與任意 LINQ 查詢整合。

主要類別: XDocument, XElement, XAttribute

在深入 LINQ to XML 之前,先認識三個基石:

  • XDocument — 整個文件的根,相當於紙本文件的封面,裡面包含所有內容。
  • XElement — 任意一個獨立的 XML 節點(元素)。就像倉庫中的一個箱子,裡面可以放其他箱子或物品(元素、屬性和文字)。
  • XAttribute — 元素的屬性,例如 <User id="42" />

視覺示意圖


+-----------------+
|   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 ?? "無 e-mail";
    string id = person.Attribute("id")?.Value ?? "無 id";
    
    Console.WriteLine($"姓名: {name}, Email: {email}, id: {id}");
}

- doc.Root 取得根元素(Contacts)。
- Elements("Person") 回傳所有 <Person>
- 接著用 .Element("...") 或 .Attribute("...") 取需要的元素/屬性。
- 運算子 ?. 可防止在找不到元素/屬性時發生錯誤。

類比

操作 XElement 就像拆套娃:拿出一個,裡面還有其他的,然後再拆。只是現在你可以用簡潔的查詢一次搞定,而不需要複雜的遍歷程式碼。

3. 用 LINQ 撰寫 XML 查詢

要取得所有人的行動電話清單?可以用 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

可以直接用 XElementXDocument 的建構子在記憶體建立 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");

註解:

- 可以用「套娃式」建構子內嵌元素:第一個參數是 tag,後面的參數是內容(可以是元素、屬性或文字)。
- 屬性通常在直接內容之前(例如 <Phone type="work">...)。

5. 與 C# 集合互動 — LINQ 的魔力

常常需要把一般物件集合(例如 List<Person>)序列化成 XML 或反向操作。這事幾乎一行 LINQ 就能搞定:

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:混淆屬性與元素。
屬性和元素是 XML 中不同的東西。資料有時會分散在兩者之間。例如:

<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