做為類別設計者,非序列化成員給你多添加工作。序列化過程中,序列化 API 不會為你初始化非序列化成員。因型別建構函式未被調用,成員初始化也不執行。用序列化特性時,非序列成員保留系統預設值:0 或 null。若預設 0 對初始化不正確,必須實作 IDeserializationCallback 介面初始化這些成員。框架在整個物件圖反序列化後調用此方法。此時可為所有非序列化成員初始化。因整個物件圖已載入,類別上所有方法調用與成員使用都安全。不幸的是非傻瓜式:框架在物件圖載入後,對圖中每個實作 IDeserializationCallback 的物件調用 OnDeserialization。物件圖中其他物件可在 OnDeserialization 進行時調用物件公用成員。若搶先,你的非序列化成員仍為 null 或 0。順序無法保證,必須確保所有公用成員能處理非序列化成員未初始化情況。
至今已知為何為所有型別添加序列化:非序列化型別在要序列化的物件中使用時帶來更多麻煩。也學會用特性實作最簡單序列化,及初始化非序列化成員。
序列化物件有方法在程式不同版本間生存。(譯註:重要問題,.NET 序列化不像 C++ 可輕鬆控制每個位元組,版本問題成常見問題。)添加序列化意味著有一天要讀取物件早期版本。Serializable 特性產生程式碼,在物件圖成員添加或移除時拋出例外。面對多版本問題,需在序列化過程中更多操作:用 ISerializable 介面。此介面定義 Hook 自訂序列化型別。介面用的方法與儲存與預設序列化方法儲存一致,即可用序列化特性。必要時再添加 ISerializable 支援。
考慮如何支援 MyType 第 2 版(添加另一欄位)。簡單添加欄位產生新型別,與磁碟上先前版本不相容:
[Serializable]
public class MyType
{
private string _label;
[NonSerialized] private int _value;
private OtherClass _object;
// Added in version 2
private int _value2; // 執行期發現版本 1.0 檔案缺此欄位拋出例外
}
實作 ISerializable 支援此行為。介面定義一個方法,但必須實作兩個。ISerializable 定義 GetObjectData() 寫資料到流。另必須提供序列化建構函式從流初始化物件:
private MyType(SerializationInfo info, StreamingContext cntxt);
序列化建構函式示範如何從先前版本讀取資料,並與預設 Serializable 特性產生的序列化保持一致,讀取當前版本資料:
[Serializable]
public sealed class MyType : ISerializable
{
private string _label;
[NonSerialized] private int _value;
private OtherClass _object;
private const int DEFAULT_VALUE = 5;
private int _value2;
private MyType(SerializationInfo info, StreamingContext cntxt)
{
_label = info.GetString("_label");
_object = (OtherClass)info.GetValue("_object", typeof(OtherClass));
try { _value2 = info.GetInt32("_value2"); }
catch (SerializationException) { _value2 = DEFAULT_VALUE; }
}
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
void ISerializable.GetObjectData(SerializationInfo inf, StreamingContext cxt)
{
inf.AddValue("_label", _label);
inf.AddValue("_object", _object);
inf.AddValue("_value2", _value2);
}
}