CodeGym /課程 /C# SELF /設定 XML 序列化

設定 XML 序列化

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

1. 介紹

你應該已經知道,.NET 預設可以根據 public 字段和屬性把物件序列化和反序列化成 XML。但當你想要產出漂亮的 XML、與別人的程式/標準相容,或是不想把某些內部細節包含到 XML 時,就需要更細緻的設定。

在 C# 裡有一組 序列化屬性 —— 特別的「標記」,用來標註類別、屬性或字段。透過這些屬性,你幾乎可以掌控一切:哪些字段會進入 XML、哪些會被忽略、元素叫什麼名字、某個值是不是要變成屬性、結構要不要複雜化,甚至元素的順序都能控制!

可以把預設序列化想像成自動填表:名字-姓氏-中間名各自放好,其他亂放。加了屬性後你就像個超級公文員:自己決定哪一欄放哪個字串,該跳過什麼,該標紅的標紅,該藏到機密欄位的也能藏起來。

物件與 XML 的對應

類別/屬性 屬性 在 XML 中的結果
[XmlRoot("Persons")]
類別 UserList
<Persons>...</Persons>
List<User> Users
[XmlElement("Person")]
<Person>...</Person>
int Id
[XmlAttribute("id")]
id="..." (屬性)
string Name
[XmlElement("FullName")]
<FullName>...</FullName>
string[] Emails
[XmlArray("EMails")][XmlArrayItem("Email")]
<EMails><Email>...</Email></EMails>
DateTime RegisteredAt
[XmlIgnore] (see RegisteredAtString) -
string RegisteredAtString
[XmlAttribute("registered")]
registered="..."
Address[] Addresses
[XmlArray("UserAddresses")][XmlArrayItem("Address")]
<UserAddresses>...</UserAddresses>

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 直接序列化,或需要額外處理。

2
任務
C# SELF, 等級 48, 課堂 1
上鎖
使用 XmlArray 和 XmlArrayItem 來序列化集合
使用 XmlArray 和 XmlArrayItem 來序列化集合
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION