什麼是序列化 (Serialization)

當宣告一個字串或數值變數並給定一些資料後,若須要將這些資料儲存起來,我想這沒多大問題,甚至自訂一個格式,日後還可以把它讀入相同型別的變數。 不過,如果這個變數是一個自訂的類別,例如 Employee ,或者一個 .NET 參考型別,如 Font、Hashtable、Datatable 類別等等,那你又會如何儲存呢? 這時,我們需要將物件資料轉成某個特定格式,這樣子才有辨法儲存至檔案、或者傳送給另一個程式、或者透過網路傳輸。 這樣子的一個轉換過程,就稱為 序列化 (Serialization)

序列化可分成二個階段:

  • 序列化 (Serializing) : 將一個 object 轉換成線性的 byte[] 資料,以方便資料的儲存與傳輸。
  • 還原序列化 (Deserializing) : 將先前序列化的資料轉換成 object。

一個序列化後的物件可以被儲存下來,等到要用的時候,只須要呼叫還原序列化的方法,這個物件就可以被完整的重建。 同樣的,將一個物件序列化成資料流後,就可以透過網路傳送給遠端的另一個程式,然後再由遠端的另一個程式進行還原序列化。

.Net 的序列化是實作在 System.Runtime.Serialization 命名空間裡,下表是三種系統提供的序列化方法,另外也可以自訂序列化方法。

  1. BinaryFormatter
    • 以二進位格式序列化和還原序列化物件。
    • 這個序列化物件最有效率的方法。
    • 它所序列化的資訊只能讓 .NET Framework 為主的應用程式讀取。
    • 命名空間 System.Runtime.Serialization.Formatters.Binary
  2. SoapFormatter:以 SOAP 格式序列化和還原序列化物件,或連接之物件的整個圖形。
  3. 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);

序列化的步驟:

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();

還原序列化的步驟:

    1. Create a stream object to read the serialized output.
    1. create a BinaryFormatter object
    1. 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);
      

如何使用 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 

如何建立可被序列化的類別

以下是使用序列化時要注意的事項:

    1. 一個類別,若要可被序列化及還原序列化,該類別需加上 Serialzable 屬性。
    1. 若沒有其他特別的標示,該類別的所有成員都會被序列化。
    1. 若有不需被序列化的成員,可對成員加上 NonSerialized 屬性。例如暫存或計算而得的成員,這類值沒有序列化的必要。
    1. 還原序列化時,若要自動讓程式對 NonSerialized 的成員進行初始化,可使用 IDeserializationCallback 介面,並實作 OnDeserialization 方法。
    1. 若類別因版本上有所的差異,在還原序列化時,會產生例外狀況。

如下面例子中的 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 這個方法

參考資料