标记为可序列化
序列化是指將對(duì)象實(shí)例的狀態(tài)存儲(chǔ)到存儲(chǔ)媒體的過(guò)程。在此過(guò)程中,先將對(duì)象的公共字段和私有字段以及類(lèi)的名稱(chēng)(包括類(lèi)所在的程序集)轉(zhuǎn)換為字節(jié)流,然后再把字節(jié)流寫(xiě)入數(shù)據(jù)流。在隨后對(duì)對(duì)象進(jìn)行反序列化時(shí),將創(chuàng)建出與原對(duì)象完全相同的副本。
在面向?qū)ο蟮沫h(huán)境中實(shí)現(xiàn)序列化機(jī)制時(shí),必須在易用性和靈活性之間進(jìn)行一些權(quán)衡。只要您對(duì)此過(guò)程有足夠的控制能力,就可以使該過(guò)程在很大程度上自動(dòng)進(jìn)行。例如,簡(jiǎn)單的二進(jìn)制序列化不能滿足需要,或者,由于特定原因需要確定類(lèi)中那些字段需要序列化。以下各部分將探討 .NET 框架提供的可靠的序列化機(jī)制,并著重介紹使您可以根據(jù)需要自定義序列化過(guò)程的一些重要功能。
持久存儲(chǔ)
我們經(jīng)常需要將對(duì)象的字段值保存到磁盤(pán)中,并在以后檢索此數(shù)據(jù)。盡管不使用序列化也能完成這項(xiàng)工作,但這種方法通常很繁瑣而且容易出錯(cuò),并且在需要跟蹤對(duì)象的層次結(jié)構(gòu)時(shí),會(huì)變得越來(lái)越復(fù)雜。可以想象一下編寫(xiě)包含大量對(duì)象的大型業(yè)務(wù)應(yīng)用程序的情形,程序員不得不為每一個(gè)對(duì)象編寫(xiě)代碼,以便將字段和屬性保存至磁盤(pán)以及從磁盤(pán)還原這些字段和屬性。序列化提供了輕松實(shí)現(xiàn)這個(gè)目標(biāo)的快捷方法。
公共語(yǔ)言運(yùn)行時(shí) (CLR) 管理對(duì)象在內(nèi)存中的分布,.NET?框架則通過(guò)使用反射提供自動(dòng)的序列化機(jī)制。對(duì)象序列化后,類(lèi)的名稱(chēng)、程序集以及類(lèi)實(shí)例的所有數(shù)據(jù)成員均被寫(xiě)入存儲(chǔ)媒體中。對(duì)象通常用成員變量來(lái)存儲(chǔ)對(duì)其他實(shí)例的引用。類(lèi)序列化后,序列化引擎將跟蹤所有已序列化的引用對(duì)象,以確保同一對(duì)象不被序列化多次。.NET?框架所提供的序列化體系結(jié)構(gòu)可以自動(dòng)正確處理對(duì)象圖表和循環(huán)引用。對(duì)對(duì)象圖表的唯一要求是,由正在進(jìn)行序列化的對(duì)象所引用的所有對(duì)象都必須標(biāo)記為?Ref="tag-863-1.html">Serializable(請(qǐng)參閱基本序列化)。否則,當(dāng)序列化程序試圖序列化未標(biāo)記的對(duì)象時(shí)將會(huì)出現(xiàn)異常。
當(dāng)反序列化已序列化的類(lèi)時(shí),將重新創(chuàng)建該類(lèi),并自動(dòng)還原所有數(shù)據(jù)成員的值。
按值封送
對(duì) 象僅在創(chuàng)建對(duì)象的應(yīng)用程序域中有效。除非對(duì)象是從 MarshalByRefObject 派生得到或標(biāo)記為 Serializable,否則,任何將對(duì)象作為參數(shù)傳遞或?qū)⑵渥鳛榻Y(jié)果返回的嘗試都將失敗。如果對(duì)象標(biāo)記為 Serializable,則該對(duì)象將被自動(dòng)序列化,并從一個(gè)應(yīng)用程序域傳輸至另一個(gè)應(yīng)用程序域,然后進(jìn)行反序列化,從而在第二個(gè)應(yīng)用程序域中產(chǎn)生出該對(duì)象的一個(gè)精確副本。此過(guò)程通常稱(chēng)為按值封送。
如果對(duì)象是從 MarshalByRefObject 派生得到,則從一個(gè)應(yīng)用程序域傳遞至另一個(gè)應(yīng)用程序域的是對(duì)象引用,而不是對(duì)象本身。也可以將從 MarshalByRefObject 派生得到的對(duì)象標(biāo)記為 Serializable。遠(yuǎn)程使用此對(duì)象時(shí),負(fù)責(zé)進(jìn)行序列化并已預(yù)先配置為 SurrogateSelector 的格式化程序?qū)⒖刂菩蛄谢^(guò)程,并用一個(gè)代理替換所有從 MarshalByRefObject 派生得到的對(duì)象。如果沒(méi)有預(yù)先配置為 SurrogateSelector,序列化體系結(jié)構(gòu)將遵從下面的標(biāo)準(zhǔn)序列化規(guī)則(請(qǐng)參閱序列化過(guò)程的步驟)。
基本序列化
要使一個(gè)類(lèi)可序列化,最簡(jiǎn)單的方法是使用?Serializable?屬性對(duì)它進(jìn)行標(biāo)記,如下所示:
[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í)例,然后調(diào)用格式化程序的 Serialize 方法。流和要序列化的對(duì)象實(shí)例作為參數(shù)提供給此調(diào)用。類(lèi)中的所有成員變量(甚至標(biāo)記為 private 的變量)都將被序列化,但這一點(diǎn)在本例中未明確體現(xiàn)出來(lái)。在這一點(diǎn)上,二進(jìn)制序列化不同于只序列化公共字段的 XML 序列化程序。
將對(duì)象還原到它以前的狀態(tài)也非常容易。首先,創(chuàng)建格式化程序和流以進(jìn)行讀取,然后讓格式化程序?qū)?duì)象進(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?效率很高,能生成非常緊湊的字節(jié)流。所有使用此格式化程序序列化的對(duì)象也可使用它進(jìn)行反序列化,對(duì)于序列化將在 .NET?平臺(tái)上進(jìn)行反序列化的對(duì)象,此格式化程序無(wú)疑是一個(gè)理想工具。需要注意的是,對(duì)對(duì)象進(jìn)行反序列化時(shí)并不調(diào)用構(gòu)造函數(shù)。對(duì)反序列化添加這項(xiàng)約束,是出于性能方面的考慮。但是,這違反了對(duì)象編寫(xiě)者通常采用的一些運(yùn)行時(shí)約定,因此,開(kāi)發(fā)人員在將對(duì)象標(biāo)記為可序列化時(shí),應(yīng)確保考慮了這一特殊約定。
如果要求具有可移植性,請(qǐng)使用 SoapFormatter。所要做的更改只是將以上代碼中的格式化程序換成 SoapFormatter,而 Serialize 和 Deserialize 調(diào)用不變。對(duì)于上面使用的示例,該格式化程序?qū)⑸梢韵陆Y(jié)果。
<SOAP-ENV:Envelope
xmlns: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)行標(biāo)記,否則將無(wú)法序列化。例如,如果試圖序列化以下類(lèi)實(shí)例,將會(huì)顯示一個(gè) SerializationException,說(shuō)明 MyStuff 類(lèi)型未標(biāo)記為可序列化。
public class MyStuff : MyObject
{
public int n3;
}
使用序列化屬性非常方便,但是它存在上述的一些限制。有關(guān)何時(shí)標(biāo)記類(lèi)以進(jìn)行序列化(因?yàn)轭?lèi)編譯后就無(wú)法再序列化),請(qǐng)參考有關(guān)說(shuō)明(請(qǐng)參閱下面的序列化規(guī)則)。
選擇性序列化
類(lèi)通常包含不應(yīng)被序列化的字段。例如,假設(shè)某個(gè)類(lèi)用一個(gè)成員變量來(lái)存儲(chǔ)線程 ID。當(dāng)此類(lèi)被反序列化時(shí),序列化此類(lèi)時(shí)所存儲(chǔ)的 ID 對(duì)應(yīng)的線程可能不再運(yùn)行,所以對(duì)這個(gè)值進(jìn)行序列化沒(méi)有意義。可以通過(guò)使用 NonSerialized 屬性標(biāo)記成員變量來(lái)防止它們被序列化,如下所示:
[Serializable]
public class MyObject
{
public int n1;
[NonSerialized] public int n2;
public String str;
}
自定義序列化
可 以通過(guò)在對(duì)象上實(shí)現(xiàn) ISerializable 接口來(lái)自定義序列化過(guò)?搞笑的qq表情?程。這一功能在反序列化后成員變量的值失效時(shí)尤其有用,但是需要為變量提供值以重建對(duì)象的完整狀態(tài)。要實(shí)現(xiàn) ISerializable,需要實(shí)現(xiàn) GetObjectData 方法以及一個(gè)特殊的構(gòu)造函數(shù),在反序列化對(duì)象時(shí)要用到此構(gòu)造函數(shù)。以下代碼示例說(shuō)明了如何在前一部分中提到的 MyObject 類(lèi)上實(shí)現(xiàn) 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ò)程中調(diào)用 GetObjectData 時(shí),需要填充方法調(diào)用中提供的 SerializationInfo 對(duì)象。只需按名稱(chēng)/值對(duì)的形式添加將要序列化的變量。其名稱(chēng)可以是任何文本。只要已序列化的數(shù)據(jù)足以在反序列化過(guò)程中還原對(duì)象,便可以自由選擇添加至 SerializationInfo 的成員變量。如果基對(duì)象實(shí)現(xiàn)了 ISerializable,則派生類(lèi)應(yīng)調(diào)用其基對(duì)象的 GetObjectData 方法。
需要強(qiáng)調(diào)的是,將?ISerializable?添加至某個(gè)類(lèi)時(shí),需要同時(shí)實(shí)現(xiàn)?GetObjectData?以及特殊的構(gòu)造函數(shù)。如果缺少GetObjectData,編譯器將發(fā)出警告。但是,由于無(wú)法強(qiáng)制實(shí)現(xiàn)構(gòu)造函數(shù),所以,缺少構(gòu)造函數(shù)時(shí)不會(huì)發(fā)出警告。如果在沒(méi)有構(gòu)造函數(shù)的情況下嘗試反序列化某個(gè)類(lèi),將會(huì)出現(xiàn)異常。在消除潛在安全性和版本控制問(wèn)題等方面,當(dāng)前設(shè)計(jì)優(yōu)于?SetObjectData?方法。例如,如果將?SetObjectData?方法定義為某個(gè)接口的一部分,則此方法必須是公共方法,這使得用戶不得不編寫(xiě)代碼來(lái)防止多次調(diào)用?SetObjectData?方法。可以想象,如果某個(gè)對(duì)象正在執(zhí)行某些操作,而某個(gè)惡意應(yīng)用程序卻調(diào)用此對(duì)象的?SetObjectData?方法,將會(huì)引起一些潛在的麻煩。
在反序列化過(guò)程中,使用出于此目的而提供的構(gòu)造函數(shù)將 SerializationInfo 傳遞給類(lèi)。對(duì)象反序列化時(shí),對(duì)構(gòu)造函數(shù)的任何可見(jiàn)性約束都將被忽略,因此,可以將類(lèi)標(biāo)記為 public、protected、internal 或 private。一個(gè)不錯(cuò)的辦法是,在類(lèi)未封裝的情況下,將構(gòu)造函數(shù)標(biāo)記為 protect。如果類(lèi)已封裝,則應(yīng)標(biāo)記為 private。要還原對(duì)象的狀態(tài),只需使用序列化時(shí)采用的名稱(chēng),從 SerializationInfo 中檢索變量的值。如果基類(lèi)實(shí)現(xiàn)了 ISerializable,則應(yīng)調(diào)用基類(lèi)的構(gòu)造函數(shù),以使基礎(chǔ)對(duì)象可以還原其變量。
如果從實(shí)現(xiàn)了 ISerializable 的類(lèi)派生出一個(gè)新的類(lèi),則只要新的類(lèi)中含有任何需要序列化的變量,就必須同時(shí)實(shí)現(xiàn)構(gòu)造函數(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);
}
}
切記要在反序列化構(gòu)造函數(shù)中調(diào)用基類(lèi),否則,將永遠(yuǎn)不會(huì)調(diào)用基類(lèi)上的構(gòu)造函數(shù),并且在反序列化后也無(wú)法構(gòu)建完整的對(duì)象。
對(duì)象被徹底重新構(gòu)建,但是在反系列化過(guò)程中調(diào)用方法可能會(huì)帶來(lái)不良的副作用,因?yàn)楸徽{(diào)用的方法可能引用了在調(diào)用時(shí)尚未反序列化的對(duì)象引用。如果正在進(jìn)行反序列化的類(lèi)實(shí)現(xiàn)了 IDeserializationCallback,則反序列化整個(gè)對(duì)象圖表后,將自動(dòng)調(diào)用 OnSerialization 方法。此時(shí),引用的所有子對(duì)象均已完全還原。有些類(lèi)不使用上述事件偵聽(tīng)器,很難對(duì)它們進(jìn)行反序列化,散列表便是一個(gè)典型的例子。在反序列化過(guò)程中檢索關(guān)鍵字/值對(duì)非常容易,但是,由于無(wú)法保證從散列表派生出的類(lèi)已反序列化,所以把這些對(duì)象添加回散列表時(shí)會(huì)出現(xiàn)一些問(wèn)題。因此,建議目前不要在散列表上調(diào)用方法。
序列化過(guò)程的步驟
在格式化程序上調(diào)用 Serialize 方法時(shí),對(duì)象序列化按照以下規(guī)則進(jìn)行:
檢查格式化程序是否有代理選取器。如果有,檢查代理選取器是否處理指定類(lèi)型的對(duì)象。如果選取器處理此對(duì)象類(lèi)型,將在代理選取器上調(diào)用 ISerializable.GetObjectData。
如果沒(méi)有代理選取器或有卻不處理此類(lèi)型,將檢查是否使用 Serializable 屬性對(duì)對(duì)象進(jìn)行標(biāo)記。如果未標(biāo)記,將會(huì)引發(fā) SerializationException。
如果對(duì)象已被正確標(biāo)記,將檢查對(duì)象是否實(shí)現(xiàn)了 ISerializable。如果已實(shí)現(xiàn),將在對(duì)象上調(diào)用 GetObjectData。
如果對(duì)象未實(shí)現(xiàn) Serializable,將使用默認(rèn)的序列化策略,對(duì)所有未標(biāo)記為 NonSerialized 的字段都進(jìn)行序列化。
版本控制
.NET 框架支持版本控制和并排執(zhí)行,并且,如果類(lèi)的接口保持一致,所有類(lèi)均可跨版本工作。由于序列化涉及的是成員變量而非接口,所以,在向要跨版本序列化的類(lèi)中添加成員變量,或從中刪除變量時(shí),應(yīng)謹(jǐn)慎行事。特別是對(duì)于未實(shí)現(xiàn) ISerializable 的類(lèi)更應(yīng)如此。若當(dāng)前版本的狀態(tài)發(fā)生了任何變化(例如添加成員變量、更改變量類(lèi)型或更改變量名稱(chēng)),都意味著如果同一類(lèi)型的現(xiàn)有對(duì)象是使用早期版本進(jìn)行序列化的,則無(wú)法成功對(duì)它們進(jìn)行反序列化。
如果對(duì)象的狀態(tài)需要在不同版本間發(fā)生改變,類(lèi)的作者可以有兩種選擇:
實(shí)現(xiàn) ISerializable。這使您可以精確地控制序列化和反序列化過(guò)程,在反序列化過(guò)程中正確地添加和解釋未來(lái)狀態(tài)。
使用 NonSerialized 屬性標(biāo)記不重要的成員變量。僅當(dāng)預(yù)計(jì)類(lèi)在不同版本間的變化較小時(shí),才可使用這個(gè)選項(xiàng)。例如,把一個(gè)新變量添加至類(lèi)的較高版本后,可以將該變量標(biāo)記為 NonSerialized,以確保該類(lèi)與早期版本保持兼容。
序列化規(guī)則
由于類(lèi)編譯后便無(wú)法序列化,所以在設(shè)計(jì)新類(lèi)時(shí)應(yīng)考慮序列化。需要考慮的問(wèn)題有:是否必須跨應(yīng)用程序域來(lái)發(fā)送此類(lèi)?是否要遠(yuǎn)程使用此類(lèi)?用戶將如何使用此類(lèi)?也許他們會(huì)從我的類(lèi)中派生出一個(gè)需要序列化的新類(lèi)。只要有這種可能性,就應(yīng)將類(lèi)標(biāo)記為可序列化。除下列情況以外,最好將所有類(lèi)都標(biāo)記為可序列化:
所有的類(lèi)都永遠(yuǎn)也不會(huì)跨越應(yīng)用程序域。如果某個(gè)類(lèi)不要求序列化但需要跨越應(yīng)用程序域,請(qǐng)從 MarshalByRefObject 派生此類(lèi)。
類(lèi)存儲(chǔ)僅適用于其當(dāng)前實(shí)例的特殊指針。例如,如果某個(gè)類(lèi)包含非受控的內(nèi)存或文件句柄,請(qǐng)確保將這些字段標(biāo)記為 NonSerialized 或根本不序列化此類(lèi)。
某些數(shù)據(jù)成員包含敏感信息。在這種情況下,建議實(shí)現(xiàn) ISerializable 并僅序列化所要求的字段
轉(zhuǎn)載于:https://www.cnblogs.com/liuzhiwei/archive/2012/10/19/2731014.html
總結(jié)
- 上一篇: rc mysql common_RR与R
- 下一篇: 计算机开关机命令,电脑自动关机命令