- 相關(guān)推薦
NET中對象序列化方法
摘 要 實(shí)現序列化最重要的兩個(gè)原因是:將對象的狀態(tài)保存在存儲媒體中以便以后重新創(chuàng )建出完全相同的副本;按值將對象從一個(gè)應用程序域發(fā)送至另一個(gè)應用程序域。例如,序列化可用于在 ASP.NET 中保存會(huì )話(huà)狀態(tài);將對象復制到 Windows 窗體的剪貼板中;它還可用于按值將對象從一個(gè)應用程序域遠程傳遞至另一個(gè)應用程序域。本文簡(jiǎn)要介紹了 Microsoft .NET 中使用的序列化。 關(guān)鍵詞 .NET;序列化;封送1 引言 序列化是指將對象實(shí)例的狀態(tài)存儲到存儲媒體的過(guò)程。在此過(guò)程中,先將對象的公共字段和私有字段以及類(lèi)的名稱(chēng)(包括類(lèi)所在的程序集)轉換為字節流,然后再把字節流寫(xiě)入數據流。在隨后對對象進(jìn)行反序列化時(shí),將創(chuàng )建出與原對象完全相同的副本。 在面向對象的環(huán)境中實(shí)現序列化機制時(shí),必須在易用性和靈活性之間進(jìn)行一些權衡。只要對此過(guò)程有足夠的控制能力,就可以使該過(guò)程在很大程度上自動(dòng)進(jìn)行。例如,簡(jiǎn)單的二進(jìn)制序列化不能滿(mǎn)足需要,或者,由于特定原因需要確定類(lèi)中哪些字段需要序列化。以下各部分將探討 .NET 框架提供的可靠的序列化機制,并著(zhù)重介紹如何根據需要自定義序列化過(guò)程。2 持久存儲 我們經(jīng)常需要將對象的字段值保存到磁盤(pán)中,并在以后在內存中還原次對象。盡管不使用序列化也能完成這項工作,但這種方法通常很繁瑣而且容易出錯,并且在需要跟蹤對象的層次結構時(shí),會(huì )變得越來(lái)越復雜?梢想象一下編寫(xiě)包含大量對象的大型業(yè)務(wù)應用程序的情形,程序員不得不為每一個(gè)對象編寫(xiě)代碼,以便將字段和屬性保存至磁盤(pán)以及從磁盤(pán)還原這些字段和屬性。序列化提供了輕松實(shí)現這個(gè)目標的快捷方法。 公共語(yǔ)言運行時(shí) (CLR) 管理對象在內存中的分布,.NET 框架則通過(guò)使用反射提供自動(dòng)的序列化機制。對象序列化后,類(lèi)的名稱(chēng)、程序集以及類(lèi)實(shí)例的所有數據成員均被寫(xiě)入存儲媒體中。對象通常用成員變量來(lái)存儲對其它實(shí)例的引用。類(lèi)序列化后,序列化引擎將跟蹤所有已序列化的引用對象,以確保同一對象不被序列化多次。.NET 框架所提供的序列化體系結構可以自動(dòng)正確處理對象圖表和循環(huán)引用。對對象圖表的惟一要求是,由正在進(jìn)行序列化的對象所引用的所有對象都必須標記為 Serializable。否則,當序列化程序試圖序列化未標記的對象時(shí)將會(huì )出現異常。當反序列化已序列化的類(lèi)時(shí),將重新創(chuàng )建該類(lèi)的對象,并自動(dòng)還原所有數據成員的值。3 按值封送 按值封送是指將對象序列化為字節流,并從一個(gè)應用程序域傳輸至另一個(gè)應用程序域,然后進(jìn)行反序列化,從而在第二個(gè)應用程序域中產(chǎn)生出該對象的一個(gè)副本,這在COM技術(shù)中經(jīng)常提到。在.Net中,對象僅在創(chuàng )建對象的應用程序域中有效,除非對象是從 MarshalByRefObject 派生得到或標記為 Serializable,否則,任何將對象作為參數傳遞或作為結果返回到另外一個(gè)應用程序域都將失敗。 如果對象標記為 Serializable,則該對象將被自動(dòng)序列化,并從一個(gè)應用程序域傳輸至另一個(gè)應用程序域,然后進(jìn)行反序列化,從而在第二個(gè)應用程序域中產(chǎn)生出該對象的一個(gè)精確副本。如果對象是從 MarshalByRefObject 派生得到,則從一個(gè)應用程序域傳遞至另一個(gè)應用程序域的是對象引用,而不是對象本身。也可以將從 MarshalByRefObject 派生得到的對象標記為 Serializable。遠程使用此對象時(shí),負責進(jìn)行序列化并已預先配置為 SurrogateSelector 的格式化程序將控制序列化過(guò)程,并用一個(gè)代理替換所有從 MarshalByRefObject 派生得到的對象。如果沒(méi)有預先配置為 SurrogateSelector,序列化體系結構將遵從下面的標準序列化規則。4 基本序列化 要使一個(gè)類(lèi)可序列化,最簡(jiǎn)單的方法是使用 Serializable 屬性對它進(jìn)行標記,如下所示:[Serializable]public class MyObject{public int n1 = 0;public int n2 = 0;public String str = null;}以下代碼片段說(shuō)明了如何將此類(lèi)的一個(gè)實(shí)例序列化為一個(gè)文件:MyObject obj = new MyObject();obj.n1 = 1;obj.n2 = 24;obj.str = "一些字符串";IFormatter formatter = new BinaryFormatter();Stream stream = new FileStream("MyFile.bin",FileMode.Create,FileAccess.Write,FileShare.None);formatter.Serialize(stream,obj);stream.Close(); 本例使用二進(jìn)制格式化程序進(jìn)行序列化。只需創(chuàng )建一個(gè)要使用的流和格式化程序的實(shí)例,然后調用格式化程序的 Serialize 方法。流和要序列化的對象實(shí)例作為參數提供給此調用。類(lèi)中包括 private 變量的所有成員變量,都將被序列化,但這一點(diǎn)在本例中未明確體現出來(lái)。在這一點(diǎn)上,二進(jìn)制序列化不同于只序列化公共字段的 XML 序列化程序。 將對象還原到它以前的狀態(tài)也非常容易。首先,創(chuàng )建格式化程序和流以進(jìn)行讀取,然后讓格式化程序對對象進(jìn)行反序列化。以下代碼片段說(shuō)明了如何進(jìn)行此操作。IFormatter formatter = new BinaryFormatter();Stream stream = new FileStream("MyFile.bin",FileMode.Open,FileAccess.Read,FileShare.Read);MyObject obj = (MyObject) formatter.Deserialize(fromStream);stream.Close();// 下面是證明Console.WriteLine("n1:{0}",obj.n1);Console.WriteLine("n2:{0}",obj.n2);Console.WriteLine("str:{0}",obj.str); 上面所使用的 BinaryFormatter 效率很高,能生成非常緊湊的字節流。所有使用此格式化程序序列化的對象也可使用它進(jìn)行反序列化,對于序列化將在 .NET 平臺上進(jìn)行反序列化的對象,此格式化程序是一個(gè)理想的工具。需要注意的是,對對象進(jìn)行反序列化時(shí)并不調用構造函數。對反序列化添加這項約束,是出于性能方面的考慮。但是,這違反了對象編寫(xiě)者通常采用的一些運行時(shí)約定,因此,開(kāi)發(fā)人員在將對象標記為可序列化時(shí),應確?紤]了這一特殊約定。 如果要求具有可移植性,應該使用 SoapFormatter。所要做的更改只是將以上代碼中的格式化程序換成 SoapFormatter,而 Serialize 和 Deserialize 調用不變。對于上面使用的示例,該格式化程序將生成以下結果。<SOAP-ENV:Envelopexmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:xsd="http://www.w3.org/2001/XMLSchema"xmlns:SOAP-ENC=http://schemas.xmlsoap.org/soap/encoding/xmlns:SOAP-ENV=http://schemas.xmlsoap.org/soap/envelope/SOAP-ENV:encodingStyle="http://schemas.microsoft.com/ soap/encoding/clr/1.0 http://schemas.xmlsoap.org/soap/ encoding/" xmlns:a1="http://schemas.microsoft.com/clr/ assem/ToFile"><SOAP-ENV:Body><a1:MyObject id="ref-1"> <n1>1</n1> <n2>24</n2> <str id="ref-3">一些字符串</str> </a1:MyObject> </SOAP-ENV:Body></SOAP-ENV:Envelope>需要注意的是,無(wú)法繼承 Serializable 屬性。如果從 MyObject 派生出一個(gè)新的類(lèi),則這個(gè)新的類(lèi)也必須使用該屬性進(jìn)行標記,否則將無(wú)法序列化。例如,如果試圖序列化以下類(lèi)實(shí)例,將會(huì )顯示一個(gè) SerializationException,說(shuō)明 MyStuff 類(lèi)型未標記為可序列化。public class MyStuff:MyObject { public int n3;}5 選擇性序列化 類(lèi)通常包含不應被序列化的字段。例如,假設某個(gè)類(lèi)用一個(gè)成員變量來(lái)存儲線(xiàn)程 ID。當此類(lèi)被反序列化時(shí),序列化此類(lèi)時(shí)所存儲的 ID 對應的線(xiàn)程可能不再運行,所以對這個(gè)值進(jìn)行序列化沒(méi)有意義?梢酝ㄟ^(guò)使用 NonSerialized 屬性標記成員變量來(lái)防止它們被序列化,如下所示:[Serializable]public class MyObject {public int n1;[NonSerialized]public int n2;public String str;}6 自定義序列化 可以通過(guò)在對象上實(shí)現 ISerializable 接口來(lái)自定義序列化過(guò)程。這一功能在反序列化后成員變量的值失效時(shí)尤其有用,但是需要為變量提供值以重建對象的完整狀態(tài)。要實(shí)現 ISerializable,需要實(shí)現 GetObjectData 方法以及一個(gè)特殊的構造函數,在反序列化對象時(shí)要用到此構造函數。以下代碼示例說(shuō)明了如何在前一部分中提到的 MyObject 類(lèi)上實(shí)現 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");}public virtual void GetObjectData(SerializationInfo info,StreamingContext context){info.AddValue("i",n1);info.AddValue("j",n2);info.AddValue("k",str);}} 在序列化過(guò)程中調用 GetObjectData 時(shí),需要填充方法調用中提供的 SerializationInfo 對象。只需按名稱(chēng)/值對的形式添加將要序列化的變量。其名稱(chēng)可以是任何文本。只要已序列化的數據足以在反序列化過(guò)程中還原對象,便可以自由選擇添加至 SerializationInfo 的成員變量。如果基對象實(shí)現了 ISerializable,則派生類(lèi)應調用其基對象的 GetObjectData 方法。 需要強調的是,將 ISerializable 添加至某個(gè)類(lèi)時(shí),需要同時(shí)實(shí)現 GetObjectData 以及特殊的構造函數。如果缺少 GetObjectData,編譯器將發(fā)出警告。但是,由于無(wú)法強制實(shí)現構造函數,所以,缺少構造函數時(shí)不會(huì )發(fā)出警告。如果在沒(méi)有構造函數的情況下嘗試反序列化某個(gè)類(lèi),將會(huì )出現異常。 在反序列化過(guò)程中,使用出于此目的而提供的構造函數將 SerializationInfo 傳遞給類(lèi)。對象反序列化時(shí),對構造函數的任何可見(jiàn)性約束都將被忽略,因此,可以將類(lèi)標記為 public、protected、internal 或 private。通常,在類(lèi)未封裝的情況下,將構造函數標記為 protect。如果類(lèi)已封裝,則應標記為 private。要還原對象的狀態(tài),只需使用序列化時(shí)采用的名稱(chēng),從 SerializationInfo 中檢索變量的值。如果基類(lèi)實(shí)現了 ISerializable,則應調用基類(lèi)的構造函數,以使基礎對象還原其變量。 如果從實(shí)現了 ISerializable 的類(lèi)派生出一個(gè)新的類(lèi),則只要新的類(lèi)中含有任何需要序列化的變量,就必須同時(shí)實(shí)現構造函數以及 GetObjectData 方法。以下代碼片段顯示了如何使用上文所示的 MyObject 類(lèi)來(lái)完成此操作。[Serializable]public class ObjectTwo:MyObject{ public int num; public ObjectTwo():base() { } protected ObjectTwo(SerializationInfo si,StreamingContext context):base(si,context) { num = si.GetInt32("num"); } public override void GetObjectData(SerializationInfo si,StreamingContext context) { base.GetObjectData(si,context); si.AddValue("num",num); }}切記要在反序列化構造函數中調用基類(lèi),否則,將永遠不會(huì )調用基類(lèi)上的構造函數,并且在反序列化后也無(wú)法構建完整的對象。7 序列化過(guò)程的規則 在格式化程序上調用 Serialize 方法時(shí),對象序列化按照以下規則進(jìn)行:檢查格式化程序是否有代理選取器。如果有,檢查代理選取器是否處理指定類(lèi)型的對象。如果選取器處理此對象類(lèi)型,將在代理選取器上調用 ISerializable.GetObjectData。 如果沒(méi)有代理選取器或有卻不處理此類(lèi)型,將檢查是否使用 Serializable 屬性對對象進(jìn)行標記。如果未標記,將會(huì )引發(fā) SerializationException。 如果對象已被正確標記,將檢查對象是否實(shí)現了 ISerializable。如果已實(shí)現,將在對象上調用 GetObjectData。 如果對象未實(shí)現 Serializable,將使用默認的序列化策略,對所有未標記為 NonSerialized 的字段都進(jìn)行序列化。8 版本控制 .NET 框架支持版本控制和并排執行,并且,如果類(lèi)的接口保持一致,所有類(lèi)均可跨版本工作。由于序列化涉及的是成員變量而非接口,所以,在向要跨版本序列化的類(lèi)中添加成員變量,或從中刪除變量時(shí),應謹慎行事。特別是對于未實(shí)現 ISerializable 的類(lèi)更應如此。若當前版本的狀態(tài)發(fā)生了任何變化(例如添加成員變量、更改變量類(lèi)型或更改變量名稱(chēng)),都意味著(zhù)如果同一類(lèi)型的現有對象是使用早期版本進(jìn)行序列化的,則無(wú)法成功對它們進(jìn)行反序列化。 如果對象的狀態(tài)需要在不同版本間發(fā)生改變,類(lèi)的作者可以有兩種選擇:實(shí)現 ISerializable。這使您可以精確地控制序列化和反序列化過(guò)程,在反序列化過(guò)程中正確地添加和解釋未來(lái)狀態(tài)。 使用 NonSerialized 屬性標記不重要的成員變量。僅當預計類(lèi)在不同版本間的變化較小時(shí),才可使用這個(gè)選項。例如,把一個(gè)新變量添加至類(lèi)的較高版本后,可以將該變量標記為 NonSerialized,以確保該類(lèi)與早期版本保持兼容。9 序列化規則 在設計新類(lèi)時(shí)應考慮序列化。需要考慮的問(wèn)題有:是否必須跨應用程序域來(lái)發(fā)送該類(lèi)的對象?是否要遠程使用此類(lèi)?用戶(hù)將如何使用此類(lèi)?是否要派生出一個(gè)需要序列化的新類(lèi)。只要有這種可能性,就應將類(lèi)標記為可序列化。除下列情況以外,最好將所有類(lèi)都標記為可序列化: 所有的類(lèi)都永遠也不會(huì )跨越應用程序域。如果某個(gè)類(lèi)不要求序列化但需要跨越應用程序域,請從 MarshalByRefObject 派生此類(lèi)。 類(lèi)存儲僅適用于其當前實(shí)例的特殊指針。例如,如果某個(gè)類(lèi)包含非受控的內存或文件句柄,請確保將這些字段標記為 NonSerialized 或根本不序列化此類(lèi)。 某些數據成員包含敏感信息。在這種情況下,建議實(shí)現 ISerializable 并僅序列化所要求的字段。 參考文獻 [1] 潘愛(ài)民著(zhù).Com原理與應用.清華大學(xué)出版社 [2] Simon Robinson K.Scott等著(zhù).C#高級編程. 清華大學(xué)出版社 [3] David J. Kruglinski Scot Wingo George Shepherd 著(zhù).VC++6.0技術(shù)內幕. 北京希望電子出版社
【NET中對象序列化方法】相關(guān)文章:
會(huì )計查證中哪些屬于有關(guān)熟悉檢查對象的方法?03-23
基于.NET技術(shù)的在線(xiàn)考試系統ASP.NET+SQL03-08
面向對象的嵌入式系統設計方法03-18
基于A(yíng)SP.NET電子購物系統ASP.NET+SQL03-08
基于A(yíng)SP.NET在線(xiàn)考試系統設計ASP.NET+SQL03-08
法律方法中的邏輯真諦03-02
面向對象設計在樓宇控制系統中的應用03-18
Home.Net模型/架構研究01-09
防范骨科護理中差錯的方法03-18