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 — 建議顯式指定編碼。
GO TO FULL VERSION