什麼是序列化 (Serialization)
當宣告一個字串或數值變數並給定一些資料後,若須要將這些資料儲存起來,我想這沒多大問題,甚至自訂一個格式,日後還可以把它讀入相同型別的變數。 不過,如果這個變數是一個自訂的類別,例如 Employee ,或者一個 .NET 參考型別,如 Font、Hashtable、Datatable 類別等等,那你又會如何儲存呢? 這時,我們需要將物件資料轉成某個特定格式,這樣子才有辨法儲存至檔案、或者傳送給另一個程式、或者透過網路傳輸。 這樣子的一個轉換過程,就稱為 序列化 (Serialization) 。
序列化可分成二個階段:
- 序列化 (Serializing) : 將一個 object 轉換成線性的 byte[] 資料,以方便資料的儲存與傳輸。
- 還原序列化 (Deserializing) : 將先前序列化的資料轉換成 object。
一個序列化後的物件可以被儲存下來,等到要用的時候,只須要呼叫還原序列化的方法,這個物件就可以被完整的重建。 同樣的,將一個物件序列化成資料流後,就可以透過網路傳送給遠端的另一個程式,然後再由遠端的另一個程式進行還原序列化。
.Net 的序列化是實作在 System.Runtime.Serialization 命名空間裡,下表是三種系統提供的序列化方法,另外也可以自訂序列化方法。
- BinaryFormatter:
- 以二進位格式序列化和還原序列化物件。
- 這個序列化物件最有效率的方法。
- 它所序列化的資訊只能讓 .NET Framework 為主的應用程式讀取。
- 命名空間 System.Runtime.Serialization.Formatters.Binary 。
- SoapFormatter:以 SOAP 格式序列化和還原序列化物件,或連接之物件的整個圖形。
- 以 XML 格式為主的序列化物件。
- 以這個方法序列化的物件是最可靠的,可以透過網路傳輸,也可以讓非 .NET 的應用程式讀取。
- SoapFormatter 比 BinaryFormatter 更可能成功地通過防火牆。
- 命名空間 System.Runtime.Serialization.Formatters.Soap 。
- XmlSerializer:
- 將物件序列化成為 XML 文件,以及從 XML 文件將物件還原序列化。
- 是一種公開的標準,以文字為主的格式,和各種平台相容性最高。
- 只能序列化公用(public)的屬性與欄位,不能序列化私用資料 (private)。
- 只能用於序列化物件,不能序列化物件圖形(object graph)。
- 命名空間 System.Xml.Serialization 。
選擇何種序列化
若確認所有使用已序列化資料的用戶端都是.NET Framework 應用程式時,才應該選擇 BinaryFormatter。例如同一支程式的寫入與讀取,使用 BinaryFormatter 就是最好的選擇。 若是會尤其他應用程式讀取你的序列化資料,或是該資料可能透過網路傳輸時,則最好使用 SoapFormatter 。 另外要注意的是,使用 SoapFormatter 產生的資訊,空間會是 BinaryFormatter 的3~4倍。
如何使用 BinaryFormatter
BinaryFormatter 建構子:
public BinaryFormatter();
public BinaryFormatter(ISurrogateSelector selector, StreamingContext context);
Serialize and Deserialize 方法:
public void Serialize(Stream serializationStream, object graph);
public void Serialize(Stream serializationStream, object graph, Header[] headers);
public object Deserialize(Stream serializationStream);
public object Deserialize(Stream serializationStream, HeaderHandler handler);
序列化的步驟:
- create a Stream object to hold the serialized output..
- create a BinaryFormatter object
- call the BinaryFormatter.Serialize method ```c# string data = “Hello World.”;
FileStream fs = new FileStream(“String.BIN”, FileMode.Create); // 建立 FileStream ,用以儲存序列化後的資料 BinaryFormatter bFormat = new BinaryFormatter(); // 建立 BinaryFormatter 物件 bFormat.Serialize(fs, data); // Serialize :序列化物件
fs.Close();
```c#
DateTime data = System.DateTime.Now;
FileStream fs = new FileStream("Date.BIN", FileMode.Create);
BinaryFormatter bFormat = new BinaryFormatter();
bFormat.Serialize(fs, System.DateTime.Now);
fs.Close();
還原序列化的步驟:
- Create a stream object to read the serialized output.
- create a BinaryFormatter object
- call the BinaryFormatter.Deserialize method to deserialize the object, and cast it to the correct type.
FileStream fs = new FileStream("String.BIN", FileMode.Open); // 建立 FileStream 開啟序列化資料 BinaryFormatter bFormat = new BinaryFormatter(); // 建立 BinaryFormatter 物件 string data = (string) bFormat.Deserialize(fs); // Deserialize :反序列化資料流中的資料,並轉成正確型別 fs.Close(); Console.WriteLine(data);
- call the BinaryFormatter.Deserialize method to deserialize the object, and cast it to the correct type.
如何使用 SoapFormatter
在使用 SoapFormatter 之前,必需先手動在專案中加入 System.Runtime.Serialization.Formatters.Soap.dll 這個組件的參考 (BinaryFormatte 預設已加入)。 接著其使用方法和 BinaryFormatter 都是一樣的,只是換成 SoapFormatter 類別即可。 SoapFormatter 執行序列化的效率較低,但比較具有相互運作性,它是 BinaryFormatter 類別的替代方案。 若需要可攜性,請使用 SoapFormatter ,若需要最大的效能,則使用 BinaryFormatter 。
NameValueCollection myDic = new NameValueCollection();
myDic.Add("apple", "a fruit ");
myDic.Add("book", "a long written or printed literary composition ");
myDic.Add("cat", "an animal ");
FileStream fs = new FileStream("NameValueCollection.SOAP", FileMode.Create);
SoapFormatter sFormat = new SoapFormatter();
sFormat.Serialize(fs, myDic);
fs.Close();
FileStream fs = new FileStream("NameValueCollection.SOAP", FileMode.Open);
SoapFormatter sFormat = new SoapFormatter();
NameValueCollection myDic = (NameValueCollection)sFormat.Deserialize(fs);
fs.Close();
foreach (string key in myDic)
{
Console.WriteLine("{0} : {1}", key, myDic[key]);
}
//apple : a fruit
//book : a long written or printed literary composition
//cat : an animal
如何建立可被序列化的類別
以下是使用序列化時要注意的事項:
- 一個類別,若要可被序列化及還原序列化,該類別需加上 Serialzable 屬性。
- 若沒有其他特別的標示,該類別的所有成員都會被序列化。
- 若有不需被序列化的成員,可對成員加上 NonSerialized 屬性。例如暫存或計算而得的成員,這類值沒有序列化的必要。
- 還原序列化時,若要自動讓程式對 NonSerialized 的成員進行初始化,可使用 IDeserializationCallback 介面,並實作 OnDeserialization 方法。
- 若類別因版本上有所的差異,在還原序列化時,會產生例外狀況。
如下面例子中的 Grade 自訂類別,若第一版只有 math, english 二個成員,但是第二版加了 chinese 這個新成員,若直接針對前版的序列化資料執行還原序列化,會產生例外狀況。有二個方法可以避免這個限制:
- 自訂序列化(custom serialization),讓它可以匯入舊版的序列化物件。
- 套用 OptionalField 屬性,以解決相容性問題。 ```c# [Serializable] //宣告成可序列化 public class Grade : IDeserializationCallback //–> 這個介面主要功能在當執行完 Deserialization 之後,通知類別 { public string name; public int math; private int english; [OptionalField] public int chinese; //[ OptionalField ]–>因序列化的資料是舊版格式,沒有這個欄位,設成[OptionalField],提供版本的相容性。 [NonSerialized] public int total; //[ NonSerialized ]–>定義這個項目不要序列化。所以,在還原序列化時這個欄位也不會被還原。常用放暫時性欄位。
public Grade(string _name, int _math, int _english, int _chinese) { name = _name; math = _math; english = _english; chinese = _chinese; total = _math + _english + _chinese; }
public string result() { if ((total) > 180) return “pass”; else return “fail”; }
//實作 IDeserializationCallback.OnDeserialization ,這樣子當 Deserialization 之後,會自動執行這段程式碼 public void OnDeserialization(object sender) { //因為chinese為[OptionalField]屬性,屬舊版沒有的欄位,若有必要,可以在此做初始化設定 if (chinese == 0) chinese = 60;
//因為total為加總欄位,沒有必要被序列化,所以當初被設定為 [NonSerialized], //但是還原後,這個欄位是有意義的,所以可以在此重新計算 total = math + english + chinese; } }
```c#
Grade grade = new Grade("vito", 80, 90, 30);
FileStream fs = new FileStream("Class.Soap", FileMode.Create);
SoapFormatter sFormat = new SoapFormatter();
sFormat.Serialize(fs, grade);
fs.Close();
FileStream fs = new FileStream("Class.Soap", FileMode.Open);
SoapFormatter bFormat = new SoapFormatter();
Grade grade = (Grade)bFormat.Deserialize(fs);
fs.Close();
Console.WriteLine(grade.result()); //還原序列化後,試試呼叫 result 這個方法
參考資料
