1. 介紹
你應該已經知道,.NET 預設可以根據 public 字段和屬性把物件序列化和反序列化成 XML。但當你想要產出漂亮的 XML、與別人的程式/標準相容,或是不想把某些內部細節包含到 XML 時,就需要更細緻的設定。
在 C# 裡有一組 序列化屬性 —— 特別的「標記」,用來標註類別、屬性或字段。透過這些屬性,你幾乎可以掌控一切:哪些字段會進入 XML、哪些會被忽略、元素叫什麼名字、某個值是不是要變成屬性、結構要不要複雜化,甚至元素的順序都能控制!
可以把預設序列化想像成自動填表:名字-姓氏-中間名各自放好,其他亂放。加了屬性後你就像個超級公文員:自己決定哪一欄放哪個字串,該跳過什麼,該標紅的標紅,該藏到機密欄位的也能藏起來。
物件與 XML 的對應
| 類別/屬性 | 屬性 | 在 XML 中的結果 |
|---|---|---|
|
類別 UserList | |
|
|
|
|
|
id="..." (屬性) |
|
|
|
|
|
|
|
[XmlIgnore] (see RegisteredAtString) | - |
|
|
|
|
|
|
2. XML 序列化常用屬性總覽
我們來看看最常用的一些屬性,這些年 .NET 使用者常用它們來裝飾自己的類別。
屬性 [XmlElement] — 元素名稱
改變在 XML 裡元素的名稱,或把屬性/字段標記為 XML 元素。
public class Person
{
[XmlElement("FullName")]
public string Name { get; set; }
}
XML:
<Person>
<FullName>瓦西里·彼得羅夫</FullName>
</Person>
如果不指定屬性 — 預設元素名稱會和屬性/字段名稱相同。
屬性 [XmlAttribute] — 序列化成 XML 屬性
把屬性/字段序列化成 XML 屬性,而不是子元素。
public class Person
{
[XmlAttribute("id")]
public int Id { get; set; }
}
XML:
<Person id="123"></Person>
在真實的 XML API 常會用屬性來傳遞識別碼、日期、旗標等。
屬性 [XmlIgnore] — 跳過字段/屬性
每個偏執狂的夢想:把某個屬性完全排除在序列化和反序列化之外!任何被標注為此的值都不會出現在最終的 XML 裡。
public class Person
{
[XmlIgnore]
public string InternalNote { get; set; }
}
XML:(這裡不會有 InternalNote 這個屬性)
屬性 [XmlArray] 與 [XmlArrayItem] — 控制集合
專門用於陣列和集合的屬性。[XmlArray] 指定外層包裝陣列的名稱,[XmlArrayItem] 指定每個元素的名稱。
public class Person
{
[XmlArray("Phones")]
[XmlArrayItem("Phone")]
public string[] PhoneNumbers { get; set; }
}
XML:
<Person>
<Phones>
<Phone>+12951234567</Phone>
<Phone>+12876543210</Phone>
</Phones>
</Person>
屬性 [XmlRoot] — 根元素名稱
用在類別上,變更 XML 的根元素名稱。
[XmlRoot("User")]
public class Person
{
public string Name { get; set; }
}
XML:
<User>
<Name>安娜</Name>
</User>
屬性 [XmlText] — 序列化為元素的文字內容
有時(或規範要求)你會希望某個屬性的值直接成為元素內的文字,而不是子元素或屬性。
public class Note
{
[XmlText]
public string Content { get; set; }
}
XML:
<Note>打電話給奶奶!</Note>
屬性 [XmlNamespaceDeclarations], [XmlElement(Type = ...)]
對於進階場景(例如帶 namespace 的序列化、支援繼承等)還有更多專用屬性。務必參考 官方的 XML 序列化文件 來看完整清單。
3. 實作:用真實範例改善序列化
我們一步步把教學用的小應用改造成「XML 序列化的高級版本」,讓它連最挑剔的 XML 檢查器都滿意。
沒有屬性的基本類別
假設我們有下面這個使用者類別:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string[] Emails { get; set; }
public DateTime RegisteredAt { get; set; }
public string Password { get; set; }
}
預設序列化會產生大概這樣的 XML(日期也會用它自己的格式):
<User>
<Id>42</Id>
<Name>Ivan Ivanov</Name>
<Emails>
<string>user@mail.com</string>
<string>admin@site.org</string>
</Emails>
<RegisteredAt>2024-07-01T00:00:00</RegisteredAt>
<Password>qwerty</Password>
</User>
問題點:
- 密碼最好根本不要序列化。
- 希望把日期當屬性,並使用較友善的格式。
- 希望 Emails 有漂亮的結構。
- 想把根元素改名為 <Person>。
透過屬性新增設定
[XmlRoot("Person")]
public class User
{
[XmlAttribute("id")]
public int Id { get; set; }
[XmlElement("FullName")]
public string Name { get; set; }
[XmlArray("EMails")]
[XmlArrayItem("Email")]
public string[] Emails { get; set; }
[XmlAttribute("registered")]
public string RegisteredAtString
{
get => RegisteredAt.ToString("yyyy-MM-dd");
set => RegisteredAt = DateTime.Parse(value);
}
[XmlIgnore]
public string Password { get; set; }
[XmlIgnore]
public DateTime RegisteredAt { get; set; }
}
細節說明:
- 我們用 [XmlAttribute("registered")] 把日期序列化為屬性。
- 為了序列化不是原生的 DateTime 而是它的字串形式,我們新增一個屬性 RegisteredAtString,同時把真實欄位用 [XmlIgnore] 隱藏起來。
- 密碼用 [XmlIgnore] 隱藏。
- Emails 陣列設定成外層標籤為 <EMails>,每個 email 則是 <Email>。
結果:
<Person id="42" registered="2024-07-01">
<FullName>Ivan Ivanov</FullName>
<EMails>
<Email>user@mail.com</Email>
<Email>admin@site.org</Email>
</EMails>
</Person>
4. 處理嵌套物件
當有嵌套物件(例如使用者跟他的地址)時,XML 的表現會更直觀。
public class Address
{
[XmlAttribute]
public string City { get; set; }
[XmlText]
public string Details { get; set; }
}
public class User
{
// ...其他屬性,見上文...
public Address[] Addresses { get; set; }
}
我們再為地址陣列加上配置:
[XmlArray("UserAddresses")]
[XmlArrayItem("Address")]
public Address[] Addresses { get; set; }
那麼 XML 會變成:
<Person id="42" registered="2024-07-01">
<FullName>Ivan Ivanov</FullName>
<EMails>
<Email>user@mail.com</Email>
</EMails>
<UserAddresses>
<Address City="Neon city">ul. Zamkovaya, d.1</Address>
<Address City="North Cave">ul. Kraynyaya d.12</Address>
</UserAddresses>
</Person>
可以看到每個 <Address> 的 city 是屬性,而地址細節則是元素內的文字。
5. 常見錯誤與注意事項
如果你對一個複雜物件做序列化,卻看不到預期的 XML 變化 —— 很可能是某個屬性沒標記好。有時候屬性/字段沒被正確標記,.NET 就會回退到預設行為。
另一個陷阱是集合名稱。如果集合(例如字串陣列)既沒有標記 [XmlArray],也沒標記 [XmlArrayItem],元素就會以 string 標籤寫入(如上面初始範例所示)。為避免這種情況,務必透過屬性明確指定標籤名稱。
對於只讀(getter-only)屬性要小心 —— 這類屬性預設不會被反序列化。盡量提供 public set。
某些類型(例如字典或介面)無法被標準的 XmlSerializer 直接序列化,或需要額外處理。
GO TO FULL VERSION