2012年1月26日 星期四

自訂序列化

當我們使用 BinaryFormatterXmlSerializer 去序列化一個類別時,系統會依設定幫我們直接產生序列化後的資訊。 但是,如果遇到版本的問題,或者某些類別不支援序列化時,導至還原序列化後的資訊不符合需求, 這時候就是使用自訂序列化的時候,也就是覆寫.NET Framework內建的序列化程式,並自行撰寫程式碼以控制序列化後的資訊。 使用自訂序列化,可以在不破壞該類別的前提下,達到上述問題的解決。

如何實作自訂序列化

若要自訂序列化,就要實作 ISerializable 介面,並且將 Serializable 屬性套用到該類別即可。

要實作 ISerializable 介面,就必須實作以下二件事:

  1. 實作 GetObjectData 方法,在這方法中填入序列化所需的資訊到 SerializationInfo 物件之中。
  2. 建立用來還原序列化時的特殊建構函式。

這個方法和建構函式的參數都是 SerializationInfoStreamingContext 型別。
GetObjectData 方法之中,主要必須撰寫的程式碼,就是使用 AddValue 方法,將想要序列化的變數,以 name/value 方式加入到 SerializationInfo 物件之中。
使 SerializationInfo 這個物件具有足夠的序列化資訊,以便在還原序列化過程中用來重建該物件。

當執行階段呼叫你的還原序列化建構函式時,它會使用在序列化過程中使用的變數名稱從 SerializationInfo 取該變數的值。

//宣告成可序列化
[Serializable]
public class Grade : ISerializable
{
    public string name;
    public int math;
    public int english;
    [OptionalField] public int chinese; 
    [NonSerialized] public int total;  

    // The standard, non-serialization constructor
    public Grade(string _name, int _math, int _english, int _chinese)
    {
        name = _name;
        math = _math;
        english = _english;
        chinese = _chinese;
        total = _math + _english + _chinese;
    }

    // 具有 SerializationInfo 和 StreamingContext 型別參數的特殊 constructor
    // 這個 constructor 在還原序列化時會使用到

    protected Grade(SerializationInfo info, StreamingContext context)
    {
        name = info.GetString("Name");
        math = info.GetInt32("MathGrade");
        english = info.GetInt32("EnglishGrade");
        chinese = info.GetInt32("ChineseGrade");
        total = math + english + chinese;
    }
    
    // 實做 GetObjectData 方法,此方法會在叫用 Serialize 方法時自動被呼叫
    // GetObjectData 的參數同 constructor

    [SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter = true)]
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Name", name);
        info.AddValue("MathGrade", english);
        info.AddValue("EnglishGrade", chinese);
        info.AddValue("ChineseGrade", total);
    }

    [OnSerializing]     //截取 OnSerializing
    void CalculateTotal(StreamingContext sc)
    {
        total = math + english + chinese;
    }

    [OnDeserialized]    //截取 OnDeserialized
    void CheckTotal(StreamingContext sc)
    {
        if (total == 0) { CalculateTotal(sc); }
    }
}

執行自訂序列化與自訂還原序列化。

//Custom Serialize (Using SoapFormatter)
private void btnSerialize_Click(object sender, EventArgs e)
{
    FileStream filestream = new FileStream("Custom_Class.SOAP", FileMode.Create);     //建立FileStream
    SoapFormatter formatter = new SoapFormatter();                                    //建立BinaryFormatter

    Grade grade = new Grade("vito", 80, 90, 30);

    formatter.Serialize(filestream, grade);       //透過 SoapFormatter 序列化物件
    filestream.Close();
}

//Custom Deserialize (Using SoapFormatter)
private void btnDeserialize_Click(object sender, EventArgs e)
{
    FileStream filestream = new FileStream("Custom_Class.SOAP", FileMode.Open);
    SoapFormatter formatter = new SoapFormatter();                    //建立SoapFormatter

    Grade grade = (Grade)formatter.Deserialize(filestream);
    filestream.Close();
}

回應序列化事件

序列化或還原序列化過程中,會引發下列四種事件。只要將這些屬性套用到回應的方法上,即可在過程中變更物件資訊。

  • OnSerializing
  • OnSerialized
  • OnDeserializing
  • OnDeserialized

回應這些事件的方法,必須符合下列要求:

  • 在方法上,套用上面想要截取的事件屬性。
  • 可接受一個 StreamingContext 物件為參數。
  • 回傳 void

XmlDocument

XmlDocument doc = new XmlDocument();
doc.Load("Log.xml");

//取得文件的根節點

XmlNode root = doc.DocumentElement;

//建立新的節點

XmlElement elem = doc.CreateElement("Log");
elem.SetAttribute("User", "vio");               //設定節點 屬性
elem.InnerText = DateTime.Now.ToString();       //設定節點 InnerText

//附加節點
root.AppendChild(elem); 

//儲存文件
doc.Save("Log.xml");
<?xml version="1.0" encoding="utf-8"?>
<Logs>
  <Log User="aaa">2012/2/13 上午 10:08:00</Log>
  <Log User="bbb">2012/2/14 上午 11:08:00</Log>
  <Log User="vio">2012/9/4 上午 10:32:36</Log>
</Logs>

沒有留言:

張貼留言