CodeGym /課程 /C# SELF /讀取與寫入 XML: XmlSerializer

讀取與寫入 XML: XmlSerializer

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

1. 介紹

XML (eXtensible Markup Language) — 可擴充的標記語言,一種流行的用來儲存與交換複雜結構化資料的格式。和 JSON 比起來,XML 不以簡潔著稱,但在銀行業、政府服務間的交換、文件以及和 1C、SAP、Oracle 等系統的整合中仍被廣泛使用。在 Windows 和 .NET 裡(例如一些 config)有些資料仍然以 XML 存放。

視覺上 XML 看起來像 HTML,但沒有固定的一組標籤(比如 <body> 或 <div>),你可以自己定義元素結構。示例:

<Person>
  <FirstName>Ivan</FirstName>
  <LastName>Petrov</LastName>
  <Age>30</Age>
</Person>

XML 基礎與和 C# 物件的對應

和 JSON 不同,XML 沒有專門的 array 或 boolean 類型——一切都用元素和屬性表示。列表結構通常透過重複相同名稱的元素來實現。

在 C# 中方便處理 XML 常用的方式是通過 XmlSerializer 做序列化/反序列化。

序列化 — 把 C# 物件轉成 XML 字串。反序列化 — 反過來,從 XML 得到 C# 物件。

如果你的類型遵守一系列要求,這個過程可以「開箱即用」,下面會詳細說明。

為什麼要用 XmlSerializer

XmlSerializer 是 .NET 的標準工具(命名空間 System.Xml.Serialization)用來在物件和 XML 之間轉換。它簡單、型別安全,適合做資料匯入/匯出、設定檔、系統間交換以及和政府服務的整合。

2. 範例:序列化物件

假設我們有個使用者模型:

public class User
{
    public string FirstName { get; set; }
    public string LastName  { get; set; }
    public int Age          { get; set; }
}

把物件序列化成 XML:

using System.Xml.Serialization;

User user = new User { FirstName = "伊凡", LastName = "彼得羅夫", Age = 30 };
// 要序列化需要指定類型
XmlSerializer serializer = new XmlSerializer(typeof(User));

// 將 XML 寫到檔案
using FileStream fs = new FileStream("user.xml", FileMode.Create);
serializer.Serialize(fs, user);

發生了什麼?

- 建立了 User 類的物件。
- 建立了針對 User 類型的 XmlSerializer 實例。
- 使用 FileStream 開啟檔案。
- 呼叫 Serialize(),把物件轉成 XML 並寫入檔案。

檔案 user.xml 的內容:

<?xml version="1.0"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <FirstName>伊凡</FirstName>
  <LastName>彼得羅夫</LastName>
  <Age>30</Age>
</User>

就這麼簡單—最少的程式碼就能得到 XML。

3. 反序列化:從 XML 讀回物件

讀取剛才的 XML 並還原成 C# 物件:

// 開啟 XML 檔案
using FileStream fs = new FileStream("user.xml", FileMode.Open);
User? loadedUser = serializer.Deserialize(fs) as User;
Console.WriteLine($"{loadedUser?.FirstName} {loadedUser?.LastName}, 年齡: {loadedUser?.Age}");

方法 Deserialize() 回傳 object,所以用 as 或強制轉型到 User。如果檔案正確且符合模型,物件會完整還原。

4. 序列化如何工作:常見特性

XML 序列化需要遵守一些規則:

• 要序列化的類需要有一個公開的預設建構子(public,無參數)。

• 要序列化的屬性必須是公開的,且需要有 getter 和 setter。私有成員會被忽略。

• 只有 getter 的屬性、靜態或抽象屬性是無法序列化的。

如果違反規則會得到 InvalidOperationException,訊息類似 "XmlSerializer cannot serialize this type"

5. 序列化集合:陣列與清單

如果要序列化多個使用者,通常用一個包裝類:

public class UserList
{
    public List<User> Users { get; set; } = new List<User>();
}

建立集合並序列化:

UserList users = new UserList
{
    Users = new List<User>
    {
        new User { FirstName = "伊凡", LastName = "彼得羅夫", Age = 30 },
        new User { FirstName = "瑪麗亞", LastName = "伊凡諾娃", Age = 25 }
    }
};

XmlSerializer serializer = new XmlSerializer(typeof(UserList));
using FileStream fs = new FileStream("users.xml", FileMode.Create);
serializer.Serialize(fs, users);

得到的 XML:

<?xml version="1.0"?>
<UserList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Users>
    <User>
      <FirstName>伊凡</FirstName>
      <LastName>彼得羅夫</LastName>
      <Age>30</Age>
    </User>
    <User>
      <FirstName>瑪麗亞</FirstName>
      <LastName>伊凡諾娃</LastName>
      <Age>25</Age>
    </User>
  </Users>
</UserList>

能不能直接序列化 List<User>? 可以,但根元素會叫做 ArrayOfUser,這在某些整合情況下不太合適。

6. 用屬性來控制序列化

可以透過屬性來控制 XML 格式:改名元素、把屬性序列化成 XML attribute、忽略欄位等。有用的屬性包括: [XmlElement][XmlAttribute][XmlArray][XmlIgnore]

序列化為屬性 (attributes)

把名字和姓氏作為屬性,年齡作為元素:

public class User
{
    [XmlAttribute]
    public string FirstName { get; set; }

    [XmlAttribute]
    public string LastName { get; set; }

    [XmlElement]
    public int Age { get; set; }
}

結果:

<User FirstName="伊凡" LastName="彼得羅夫">
  <Age>30</Age>
</User>

控制元素名稱

把標籤改成更好讀的名字:

public class User
{
    [XmlElement("名字")]
    public string FirstName { get; set; }

    [XmlElement("姓氏")]
    public string LastName { get; set; }

    [XmlElement("年齡")]
    public int Age { get; set; }
}

生成的 XML:

<User>
  <名字>伊凡</名字>
  <姓氏>彼得羅夫</姓氏>
  <年齡>30</年齡>
</User>

忽略屬性

如果不想序列化某個屬性,標記為 [XmlIgnore]

public class User
{
    public string FirstName { get; set; }

    [XmlIgnore]
    public string InternalCode { get; set; }
}

7. 序列化層級結構與巢狀物件

巢狀的類別會被序列化成巢狀的 XML 元素:

public class Address
{
    public string City { get; set; }
    public string Street { get; set; }
}

public class User
{
    public string FirstName { get; set; }
    public Address Address { get; set; }
}
var user = new User
{
    FirstName = "安娜",
    Address = new Address { City = "霓虹城", Street = "榆樹街" }
};

XmlSerializer serializer = new XmlSerializer(typeof(User));
using var fs = new FileStream("user_with_address.xml", FileMode.Create);
serializer.Serialize(fs, user);

產生的 XML:

<User>
  <FirstName>安娜</FirstName>
  <Address>
    <City>霓虹城</City>
    <Street>榆樹街</Street>
  </Address>
</User>

8. 序列化到/從字串(不經檔案)

序列化到字串

using System.Text;

var serializer = new XmlSerializer(typeof(User));
using var stringWriter = new StringWriter();

serializer.Serialize(stringWriter, user);
string xml = stringWriter.ToString();
Console.WriteLine(xml);

從字串反序列化

var xml = "<User><FirstName>安娜</FirstName></User>";
using var stringReader = new StringReader(xml);
User user = (User)serializer.Deserialize(stringReader);
Console.WriteLine(user.FirstName);

9. 範例:應用資料的匯出與匯入

將使用者清單匯出/匯入到 XML 檔案:

public static void ExportUsers(UserList users, string path)
{
    var serializer = new XmlSerializer(typeof(UserList));
    using var fs = new FileStream(path, FileMode.Create);
    serializer.Serialize(fs, users);
}

public static UserList ImportUsers(string path)
{
    var serializer = new XmlSerializer(typeof(UserList));
    using var fs = new FileStream(path, FileMode.Open);
    return (UserList)serializer.Deserialize(fs);
}

用途: 備份、與其他服務交換、整合。在面試中關於 XmlSerializer 的問題並不罕見——特別是在和政府服務、銀行打交道的公司。

10. 常見錯誤與工作上的注意點

如果欄位不是 public,它不會被序列化。

只有 getter 的屬性會被忽略。

屬性類型如果是介面或抽象類別,序列化器無法處理。

類型 DateTime 會以 XSD 格式序列化(參見 文件)。

像陣列與 List<T> 這類集合通常表現如預期,但 ObservableCollection<T> 在某些第三方系統中可能會有意外。

編碼細節:如果使用 StreamWriter 輸出但沒有明確指定 encoding,結果可能是 UTF-16;為了相容性通常期待 UTF-8 — 建議顯式指定編碼。

2
任務
C# SELF, 等級 48, 課堂 0
上鎖
序列化物件清單
序列化物件清單
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION