自訂序列化
您可以在物件上實作 ISerializable 介面,自訂序列化程序。這在成員變數的值於還原序列化之後成為無效的情形中特別有用,但您必須將數值提供給變數以便重新建構物件的完整狀態。此外,您不應該將預設序列化用於標記了 Serializable 屬性、而且在類別層級或建構函式上有宣告式或命令式安全性的類別。相反的,這些類別應該永遠實作 ISerializable 介面。
實作 ISerializable 牽涉到實作 GetObjectData 方法,以及將在物件還原序列化時使用的特殊建構函式。以下範例程式碼示範如何實作先前章節中 MyObject
類別上的 ISerializable。
[Serializable]
public class MyObject : ISerializable
{
public int n1;
public int n2;
public String str;
public MyObject()
{
}
protected MyObject(SerializationInfo info, StreamingContext context)
{
n1 = info.GetInt32("i");
n2 = info.GetInt32("j");
str = info.GetString("k");
}
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter
=true)]
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("i", n1);
info.AddValue("j", n2);
info.AddValue("k", str);
}
}
在序列化期間呼叫 GetObjectData 時,您要負責填入 (Populate) 由方法呼叫提供的 SerializationInfo。只是將要被序列化的變數當作名稱/值配對來加入即可。任何文字都可以用來做為名稱。只要在還原序列化期間有充分的資料被序列化來還原物件,您便可以自由決定要將哪個成員變數加入至 SerializationInfo。如果基底物件實作 ISerializable 的話,衍生類別 (Derived Class) 便會呼叫基底物件上的 GetObjectData 方法。
請注意,序列化可以允許其他程式碼查看或修改其他方式無法存取的物件執行個體資料。因此,執行序列化的程式碼需要指定 SerializationFormatter 旗標的 SecurityPermission。依照預設原則,這個使用權限不會授與給 Internet 下載或 Intranet 的程式碼;只有本機電腦上的程式碼才會被授與這個使用權限。應該要求指定了 SerializationFormatter 旗標的 SecurityPermission 或要求其他專門保護私用資料的使用權限,以明確保護 GetObjectData 方法。
如果私用欄位儲存敏感資訊,您應該要求 GetObjectData 上的適當使用權限來保護資料。請記住,如果程式碼被授與指定了 SerializationFormatter 旗標的 SecurityPermission,就可以檢視和修改私用欄位中儲存的資料。被授與此 SecurityPermission 的惡意呼叫端可以檢視如隱藏目錄位置或授與使用權限的資料,並使用資料來利用電腦上的安全性弱點。如需您可以指定的安全性使用權限旗標的完整清單,請參閱 SecurityPermissionFlag 列舉型別。
務必要注意,將 ISerializable 加入類別時,您必須同時實作 GetObjectData 和特殊的建構函式。編譯器將警告您是否遺漏了 GetObjectData。然而,因為無法強制實作建構函式,所以如果沒有建構函式,將不提供警告,而且在沒有建構函式時嘗試將類別還原序列化將會擲回例外狀況。
為了避開潛在的安全性和版本控制問題,目前的設計比 SetObjectData 方法更為人喜愛。例如,如果 SetObjectData 方法被定義為介面的一部份,它必須是公用的;這樣一來,使用者就必須撰寫程式碼,以防 SetObjectData 方法被多次呼叫。否則,在執行作業的過程中,如果惡意的應用程式呼叫物件上的 SetObjectData 方法,可能就會導致潛在的問題。
在還原序列化期間,SerializationInfo 會傳遞給使用建構函式 (為此目的而提供的) 的類別。任何加諸建構函式的可視性條件約束在物件還原序列化時會被忽略,所以您可以將類別標記為公用的、保護的 (Protected)、內部的或私用的。然而,除非類別是密封的,否則最好讓建構函式成為受保護的,此時建構函式應該標記為私用的。建構函式也應該執行完整的輸入驗證。若要避免惡意程式碼的不當使用,建構函式應該強制相同的安全性檢查和必要的使用權限,以取得使用任何其他建構函式的類別執行個體。如果不遵循這個建議,惡意程式碼可能會略過在使用公開建構函式進行標準執行個體建構時套用的安全性,預先序列化物件、使用指定了 SerializationFormatter 旗標的 SecurityPermission 取得控制,以及在用戶端電腦上將物件還原序列化。
若要還原物件的狀態,只需使用序列化期間所用的名稱從 SerializationInfo 擷取變數的值。如果基底類別 (Base Class) 實作 ISerializable,應該呼叫基底建構函式來允許基底物件還原其變數。
當您從實作 ISerializable 的類別衍生新類別時,如果衍生類別具有任何需要序列化的變數,它就必須實作建構函式以及 GetObjectData 方法。下面的程式碼範例示範如何使用之前示範的 MyObject
類別來做到這點。
[Serializable]
public class ObjectTwo : MyObject
{
public int num;
public ObjectTwo() : base()
{
}
protected ObjectTwo(SerializationInfo si, StreamingContext context) : base(si,context)
{
num = si.GetInt32("num");
}
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter
=true)]
public override void GetObjectData(SerializationInfo si, StreamingContext context)
{
base.GetObjectData(si,context);
si.AddValue("num", num);
}
}
不要忘記在還原序列化建構函式中呼叫基底類別;如果沒有這麼做,將永遠不會呼叫基底類別上的建構函式,而且無法在還原序列化之後完整建構物件。
物件會由內而外重新建構,因為呼叫的方法可能會參照到進行呼叫之前尚未還原序列化的物件參考,因此在還原序列化期間呼叫方法可能會產生不想要的副作用 (Side Effect)。如果正在還原序列化的類別實作 IDeserilizationCallback,在整個物件 Graph 已還原序列化時,會自動呼叫 OnDeserialization 方法。這個時候,所有受參考的子物件都已經完整還原。雜湊表是類別的典型範例,若不使用上述的事件接聽項 (Event Listener),即難以還原序列化。在還原序列化期間擷取索引鍵/值配對是很容易的,但因為不能保證衍生自雜湊表的類別已經被還原序列化,所以將這些物件加回到雜湊表可能會產生問題。因此在這階段呼叫雜湊表上的方法並不適當。
請參閱
二進位序列化 | 使用 .NET 遠端處理存取其他應用程式定義域中的物件 | XML 和 SOAP 序列化 | 安全性和序列化 | 程式碼存取安全性