1. 程式人生 > >Serializable 指示一個類可以序列化;ICloneable支持克隆,即用與現有實例相同的值創建類的新實例(接口);ISerializable允許對象控制其自己的序列化和反序列化過程(接口)

Serializable 指示一個類可以序列化;ICloneable支持克隆,即用與現有實例相同的值創建類的新實例(接口);ISerializable允許對象控制其自己的序列化和反序列化過程(接口)

att 文本 所有 可能 成員 強制 void inter 適用於

Serializable

序列化是指將對象實例的狀態存儲到存儲媒體的過程。在此過程中,先將對象的公共字段和私有字段以及類的名稱(包括類所在的程序集)轉換為字節流,然後再把字節流寫入數據流。在隨後對對象進行反序列化時,將創建出與原對象完全相同的副本

在 面向對象的環境中實現序列化機制時,必須在易用性和靈活性之間進行一些權衡。只要您對此過程有足夠的控制能力,就可以使該過程在很大程度上自動進行。例 如,簡單的二進制序列化不能滿足需要,或者,由於特定原因需要確定類中那些字段需要序列化。

基本序列化
要使一個類可序列化,最簡單的方法是使用 Serializable 屬性對它進行標記,如下所示:

技術分享圖片
**************************************************************************************

[Serializable]
public Class MyObject { public int n1 = 0; public int n2 = 0; public String str = null; } **************************************************************************************
Serializable

以下代碼片段說明了如何將此類的一個實例序列化為一個文件:

技術分享圖片
1 MyObject obj = new MyObject();
2 obj.n1 = 1;
3 obj.n2 = 24;
4 obj.str = "
一些字符串"; 5 IFormatter formatter = new BinaryFormatter(); 6 Stream stream = new FileStream("MyFile.bin", FileMode.Create,FileAccess.Write, FileShare.None); 7 formatter.Serialize(stream, obj); 8 stream.Close();
實例序列化為一個文件

本例使用二進制格式化程序進行序列化。您只需創建一個要使用的流和格式化程序的實例,然後調用格式化程序的 Serialize 方法。流和要序列化的對象實例作為參數提供給此調用。類中的所有成員變量(甚至標記為 private 的變量)都將被序列化,但這一點在本例中未明確體現出來。在這一點上,二進制序列化不同於只序列化公共字段的 XML 序列化程序。

將對象還原到它以前的狀態也非常容易。首先,創建格式化程序和流以進行讀取,然後讓格式化程序對對象進行反序列化。以下代碼片段說明了如何進行此操作。

技術分享圖片
1 IFormatter formatter = new BinaryFormatter();
2 Stream stream = new FileStream("MyFile.bin", FileMode.Open,FileAccess.Read, FileShare.Read);
3 MyObject obj = (MyObject) formatter.Deserialize(fromStream);
4 stream.Close();
5 
6 // 下面是輸出驗證
7 Console.WriteLine("n1: {0}", obj.n1);
8 Console.WriteLine("n2: {0}", obj.n2); Console.WriteLine("str: {0}", obj.str);
反序列化

自定義序列化

可以通過在對象上實現 ISerializable 接口來自定義序列化過程。這一功能在反序列化後成員變量的值失效時尤其有用,但是需要為變量提供值以重建對象的完整狀態。要實現 ISerializable,需要實現 GetObjectData 方法以及一個特殊的構造函數,在反序列化對象時要用到此構造函數。以下代碼示例說明了如何在前一部分中提到的 MyObject 類上實現 ISerializable。

技術分享圖片
 1 [Serializable]
 2 public class MyObject : ISerializable
 3 {
 4    public int n1;
 5    public int n2;
 6    public String str;
 7 
 8    public MyObject()
 9    {
10    }
11 
12    protected MyObject(SerializationInfo info, StreamingContext context)
13    {
14    n1 = info.GetInt32("i");
15    n2 = info.GetInt32("j");
16    str = info.GetString("k");
17    }
18 
19    public virtual Void GetObjectData(SerializationInfo info,
20 StreamingContext context)
21    {
22    info.AddValue("i", n1);
23    info.AddValue("j", n2);
24    info.AddValue("k", str);
25    }
26 }
序列化接口

序列化過程中調用 GetObjectData 時,需要填充方法調用中提供的 SerializationInfo 對象。只需按名稱/值對的形式添加將要序列化的變量。其名稱可以是任何文本。只要已序列化的數據足以在反序列化過程中還原對象,便可以自由選擇添加至 SerializationInfo 的成員變量。如果基對象實現了 ISerializable,則派生類應調用其基對象的 GetObjectData 方法。

需要強調的是,將 ISerializable 添加至某個類時,需要同時實現 GetObjectData 以及特殊的構造函數。如果缺少 GetObjectData,編譯器將發出警告。但是,由於無法強制實現構造函數,所以,缺少構造函數時不會發出警告。如果在沒有構造函數的情況下嘗試反 序列化某個類,將會出現異常。在消除潛在安全性和版本控制問題等方面,當前設計優於 SetObjectData 方法。例如,如果將 SetObjectData 方法定義為某個接口的一部分,則此方法必須是公共方法,這使得用戶不得不編寫代碼來防止多次調用 SetObjectData 方法。可以想象,如果某個對象正在執行某些操作,而某個惡意應用程序卻調用此對象的 SetObjectData 方法,將會引起一些潛在的麻煩。

在反序列化過程中,使用出於此目的而提供的構造函數將 SerializationInfo 傳遞給類。對象反序列化時,對構造函數的任何可見性約束都將被忽略,因此,可以將類標記為 public、protected、internal 或 private。一個不錯的辦法是,在類未封裝的情況下,將構造函數標記為 protect。如果類已封裝,則應標記為 private。要還原對象的狀態,只需使用序列化時采用的名稱,從 SerializationInfo 中檢索變量的值。如果基類實現了 ISerializable,則應調用基類的構造函數,以使基礎對象可以還原其變量。

如果從實現了 ISerializable 的類派生出一個新的類,則只要新的類中含有任何需要序列化的變量,就必須同時實現構造函數以及 GetObjectData 方法。以下代碼片段顯示了如何使用上文所示的 MyObject 類來完成此操作。

切記要在反序列化構造函數中調用基類,否則,將永遠不會調用基類上的構造函數,並且在反序列化後也無法構建完整的對象。

對 象被徹底重新構建,但是在反系列化過程中調用方法可能會帶來不良的副作用,因為被調用的方法可能引用了在調用時尚未反序列化的對象引用。如果正在進行反序 列化的類實現了 IDeserializationCallback,則反序列化整個對象圖表後,將自動調用 OnSerialization 方法。此時,引用的所有子對象均已完全還原。有些類不使用上述事件偵聽器,很難對它們進行反序列化,散列表便是一個典型的例子。在反序列化過程中檢索關鍵 字/值對非常容易,但是,由於無法保證從散列表派生出的類已反序列化,所以把這些對象添加回散列表時會出現一些問題。因此,建議目前不要在散列表上調用方 法。

序列化過程的步驟
在格式化程序上調用 Serialize 方法時,對象序列化按照以下規則進行:

檢查格式化程序是否有代理選取器。如果有,檢查代理選取器是否處理指定類型的對象。如果選取器處理此對象類型,將在代理選取器上調用 ISerializable.GetObjectData。
如果沒有代理選取器或有卻不處理此類型,將檢查是否使用 Serializable 屬性對對象進行標記。如果未標記,將會引發 SerializationException。
如果對象已被正確標記,將檢查對象是否實現了 ISerializable。如果已實現,將在對象上調用 GetObjectData。
如果對象未實現 Serializable,將使用默認的序列化策略,對所有未標記為 NonSerialized 的字段都進行序列化。

版本控制
.NET 框架支持版本控制和並排執行,並且,如果類的接口保持一致,所有類均可跨版本工作。由於序列化涉及的是成員變量而非接口,所以,在向要跨版本序列化的類中 添加成員變量,或從中刪除變量時,應謹慎行事。特別是對於未實現 ISerializable 的類更應如此。若當前版本的狀態發生了任何變化(例如添加成員變量、更改變量類型或更改變量名稱),都意味著如果同一類型的現有對象是使用早期版本進行序 列化的,則無法成功對它們進行反序列化。

如果對象的狀態需要在不同版本間發生改變,類的作者可以有兩種選擇:

實現 ISerializable。這使您可以精確地控制序列化和反序列化過程,在反序列化過程中正確地添加和解釋未來狀態。
使用 NonSerialized 屬性標記不重要的成員變量。僅當預計類在不同版本間的變化較小時,才可使用這個選項。例如,把一個新變量添加至類的較高版本後,可以將該變量標記為 NonSerialized,以確保該類與早期版本保持兼容。

序列化規則
由於類編譯後便無法序列化,所以在設計新類時應考慮序列化。需要考慮的問題有:是否必須跨應用程序域來發送此類?是否要遠程使用此類?用戶將如何使用此類? 也許他們會從我的類中派生出一個需要序列化的新類。只要有這種可能性,就應將類標記為可序列化。除下列情況以外,最好將所有類都標記為可序列化:

所有的類都永遠也不會跨越應用程序域。如果某個類不要求序列化但需要跨越應用程序域,請從 MarshalByRefObject 派生此類。
類存儲僅適用於其當前實例的特殊指針。例如,如果某個類包含非受控的內存或文件句柄,請確保將這些字段標記為 NonSerialized 或根本不序列化此類。
某些數據成員包含敏感信息。在這種情況下,建議實現 ISerializable 並僅序列化所要求的字段。

技術分享圖片
 1 [Serializable]
 2 public class ObjectTwo : MyObject
 3 {
 4    public int num;
 5 
 6    public ObjectTwo() : base()
 7    {
 8    }
 9 
10    protected ObjectTwo(SerializationInfo si, StreamingContext context) :base(si,context)
11    {
12    num = si.GetInt32("num");
13    }
14 
15    public override void GetObjectData(SerializationInfo si,
16 StreamingContext context)
17    {
18    base.GetObjectData(si,context);
19    si.AddValue("num", num);
20    }
21 }
View Code

Serializable 指示一個類可以序列化;ICloneable支持克隆,即用與現有實例相同的值創建類的新實例(接口);ISerializable允許對象控制其自己的序列化和反序列化過程(接口)