摘要:.NET 筆記:序列化
作者:蔡煥麟
日期:Nov-5-2005
簡介
序列化(serialization)是一種將物件的狀態經過一道轉換的程序,儲存至另一個儲存媒體的技術;反序列化則是相反的程序,也就是將之前儲存的序列化資料還原成物件。
例如,你可以把一塊冰塊融化成水,然後透過水管流到瓶子裡的過程看成是序列化,而將瓶子裡的水透過水管倒回製冰盒凝結成冰塊的過程,則可視為反序列化。
用途
序列化/反序列化的主要用途,在於將物件在執行時期的狀態(在記憶體中的樣子)予以保留,所謂的保留,通常就是儲存在其他儲存媒體,例如:硬碟,以便下次(例如:重新開機後)可以再透過反序列化的程序恢復物件的狀態。
序列化的方法
序列化的方法主要有三種:
使用串流物件自行撰寫程式儲存物件的狀態;
使用 Serializable 特徵項與 formatter 類別;
使用 XmlSerializer 類別。
第一種方式最麻煩,這裡不多作介紹,如果您有興趣的話,可以試試看自行用 StreamReader/StreamWriter 或 BinaryReader/BinaryWriter 將物件的狀態儲存成文字格式或二進位格式的檔案,您會發現這種方式要寫很多程式碼,而且日後的維護也挺麻煩。
以下介紹其他兩種方式。
方法一:使用 Serializable 特徵項與 formatter 類別
這是最簡單的方式,主要的步驟為:
將類別以 [Serializable] 特徵項標示為可序列化的。注意使用此特徵項時,程式必須匯入命名空間:System.Runtime.Serialization。
使用 formatter 將物件序列化至特定的串流。
Formatter 物件會在稍後介紹,先來看看如何將類別標示為可序列化的,參考以下範例:
using System.Runtime.Serialization; ....
[Serializable] public class Employee { public string name; public int age; private double salary; // 注意這個成員是 private }
類別已經標示為可序列化之後,接著便可使用 formatter 物件將物件進行序列化或反序列化。
Formatter 物件的任務有二:
把物件的欄位資料進行序列化,包括 private, protected, public 成員,都可以被序列化,而且物件中的巢狀物件也會被序列化,因此這種方式又稱為深層序列化。
把各種型態的資料轉換成 byte 層級的資料。
.NET framework 提供兩種 formatter 類別:
BinaryFormatter
SoapFormatter
BinaryFormatter 會將物件的狀態資料轉換成二進位格式的資料;SoapFormatter 則是將物件的狀態資料轉換成 SOAP 的文字格式,雖然 SOAP 是 Web Services 的標準,但 SoapFormatter 跟 Web Services 並沒有什麼關係,只是它序列化的結果方便人閱讀而已。
這兩種 formatter 都實作了 IFormatter 介面:
public interface IFormatter { void Serialize(Stream serializationStream, object graph); object Deserialize(Stream serializationStream); // 省略其他不常用的成員 }
其中 Serialize 方法就是將物件序列化至指定的串流,而 Deserialize 方法則是反序列化。還記得前面筆者舉的冰塊的例子嗎?你可以把這裡的串流想成是那條水管。
BinaryFormatter 範例
以下示範用 BinaryFormatter 將前面定義的 Employee 類別的物件資料序列化:
using System.Runtime.Serialization.Formatters.Binary .... private void btnSerialize_Click(object sender, System.EventArgs e) { // 示範如何將物件利用 BinaryFormatter 序列化到檔案中 Employee emp = new Employee(); emp.name = "Will Tsai"; emp.age = 30; FileStream fs = new FileStream(@"c:\0\test.bin", FileMode.Create); IFormatter formatter = new BinaryFormatter(); formatter.Serialize(fs, emp); fs.Close(); }
以下是反序列化的範例:
private void btnDeserialize_Click(object sender, System.EventArgs e) { // 示範如何利用 BinaryFormatter 進行反序列化 FileStream fs = new FileStream(@"c:\0\test.bin", FileMode.Open); IFormatter formatter = new BinaryFormatter(); // 注意不需要 new Employee 物件,反序列化時就會建立物件實體 Employee emp = (Employee) formatter.Deserialize(fs); MessageBox.Show(emp.name); fs.Close(); }
SoapFormatter 範例
以下是使用 SoapFormatter 序列化物件的範例:
using System.Runtime.Serialization.Formatters.Soap .... private void btnSerialize_Click(object sender, System.EventArgs e) { // 示範如何將物件利用 SoapFormatter 序列化到檔案中 Employee emp = new Employee(); emp.name = "Will Tsai"; emp.age = 30; FileStream fs = new FileStream(@"c:\0\test.soap", FileMode.Create); IFormatter formatter = new SoapFormatter(); formatter.Serialize(fs, emp); fs.Close(); }
序列化產生的檔案內容如下:
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <a1:Employee id="ref-1" xmlns:a1="...(略)"> <name id="ref-3">Will Tsai</name>
<age>30</age>
<salary>0</salary> </a1:Employee> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
反序列化的寫法跟前面 BinaryFormatter 的範例類似,這裡就不列出了。
注意:在使用 SoapFormatter 時,專案必須先加入組件參考:System.Runtime.Serialization.Formatters.Soap。
指定某些成員不要序列化
由於 Serializable 特徵項會告知 formatter 物件將所有資料成員序列化,可是有時候我們會希望某些成員不要被序列化,例如:動態計算或者暫時性的資料成員,這時後可以在資料成員前面加上 NonSerialized 特徵項,例如:
[Serializable] public class Employee { public string name; [NonSerialized] public int age; private double salary; // 注意這個成員是 private }
方法二:使用 XmlSerializer 類別
使用 XmlSerializer 類別序列化物件時,被序列化的物件,其類別無須加上 Serializable 特徵項。以下是序列化範例:
using System.Xml.Serialization; .... private void btnXmlSerialize_Click(object sender, System.EventArgs e) { // 示範如何利用 XmlSerializer 序列化物件 Employee emp = new Employee(); emp.name = "Will Tsai"; emp.age = 30; FileStream fs = new FileStream(@"c:\0\test.xml", FileMode.Create); XmlSerializer xs = new XmlSerializer(typeof(Employee)); xs.Serialize(fs, emp); fs.Close(); }
序列化的檔案內容如下:
<?xml version="1.0"?> <Employee xmlns:xsd="...(略)"> <name>Will Tsai</name>
<age>30</age>
<gender>false</gender> </Employee>
跟前面用 SoapFormatter 序列化的結果比較看看,兩者有何不同?
使用 XmlSerializer 序列化 Employee 物件的結果少了 salary 這個欄位,因為這種方法無法序列化 private、protected 層級的資料成員。此外,XmlSerializer 也不會針對物件內部的巢狀物件作處理,這種方式稱為淺層序列化。
注意:Employee 類別的存取範圍必須是 public,否則在使用 XmlSerializer 執行序列化時會發生異常(exception)。 |
以下是反序列化範例:
private void btnXmlDeserialize_Click(object sender, System.EventArgs e) { // 示範如何利用 XmlSerializer 序列化物件 FileStream fs = new FileStream(@"c:\0\test.xml", FileMode.Open); XmlSerializer xs = new XmlSerializer(typeof(Employee));
Employee emp = (Employee) xs.Deserialize(fs); MessageBox.Show(emp.name); fs.Close(); }
結語
本文介紹了兩種序列化即反序列化物件的作法,使用 Serializable 搭配 formatter 的作法是最簡單的,而且可以處理深層序列化(可序列化巢狀物件),由於 formatter 是透過 Reflection 技術取得型別資訊,因此資料成員不管是 public、protected、還是 private,都可以序列化。另一種是使用 XmlSerializer 物件執行淺層的序列化,這種方式只會處理 public 資料成員。
有些進階議題並沒有在本文中提及,例如:IDeserializationCallback 和 ISerializable,這兩個介面都可以讓你更彈性的處理序列化的動作,如果有興趣進一步研究,可以用 Google 尋找相關資料。