.Net里的序列化
2024-07-10 12:57:55
供稿:網(wǎng)友
 
什么是序列化?
---.net的運(yùn)行時(shí)環(huán)境用來(lái)支持用戶定義類型的流化的機(jī)制。它是將對(duì)象實(shí)例的狀態(tài)存儲(chǔ)到存儲(chǔ)媒體的過(guò)程。在此過(guò)程中,先將對(duì)象的公共字段和私有字段以及類的名稱(包括類所在的程序集)轉(zhuǎn)換為字節(jié)流,然后再把字節(jié)流寫入數(shù)據(jù)流。在隨后對(duì)對(duì)象進(jìn)行反序列化時(shí),將創(chuàng)建出與原對(duì)象完全相同的副本。
序列化的目的:
1、以某種存儲(chǔ)形式使自定義對(duì)象持久化;
2、將對(duì)象從一個(gè)地方傳遞到另一個(gè)地方。
實(shí)質(zhì)上序列化機(jī)制是將類的值轉(zhuǎn)化為一個(gè)一般的(即連續(xù)的)字節(jié)流,然后就可以將該流寫到磁盤文件或任何其他流化目標(biāo)上。而要想實(shí)際的寫出這個(gè)流,就要使用那些實(shí)現(xiàn)了iformatter接口的類里的serialize和deserialize方法。
在.net框架里提供了這樣兩個(gè)類:
一、binaryformatter
binaryformatter使用二進(jìn)制格式化程序進(jìn)行序列化。您只需創(chuàng)建一個(gè)要使用的流和格式化程序的實(shí)例,然后調(diào)用格式化程序的 serialize 方法。流和要序列化的對(duì)象實(shí)例作為參數(shù)提供給此調(diào)用。類中的所有成員變量(甚至標(biāo)記為 private 的變量)都將被序列化。
首先我們創(chuàng)建一個(gè)類:
[serializable]
public class myobject {
 public int n1 = 0;
 public int n2 = 0;
 public string str = null;
}
serializable屬性用來(lái)明確表示該類可以被序列化。同樣的,我們可以用nonserializable屬性用來(lái)明確表示類不能被序列化。
接著我們創(chuàng)建一個(gè)該類的實(shí)例,然后序列化,并存到文件里持久:
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();
而將對(duì)象還原到它以前的狀態(tài)也非常容易。首先,創(chuàng)建格式化程序和流以進(jìn)行讀取,然后讓格式化程序?qū)?duì)象進(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);
二、soapformatter
前面我們用binaryformatter以二進(jìn)制格式來(lái)序列化。很容易的我們就能把前面的例子改為用soapformatter的,這樣將以xml格式化,因此能有更好的可移植性。所要做的更改只是將以上代碼中的格式化程序換成 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è)新的類,則這個(gè)新的類也必須使用該屬性進(jìn)行標(biāo)記,否則將無(wú)法序列化。例如,如果試圖序列化以下類實(shí)例,將會(huì)顯示一個(gè) serializationexception,說(shuō)明 mystuff 類型未標(biāo)記為可序列化。
public class mystuff : myobject 
{
 public int n3;
}
然而關(guān)于格式化器,還有個(gè)問(wèn)題,假設(shè)我們只需要xml,但不需要soap特有的額外信息,那么該怎么做?有兩個(gè)方案:1、編寫一個(gè)實(shí)現(xiàn)iformatter接口的類,采用的方式類似于soapformatter,但是可以沒(méi)有你不需要的信息;2、使用框架提供的類xmlserializer。
xmlserializer類和前兩個(gè)主流的序列化類的幾個(gè)不同點(diǎn)是:
1、不需要serializable屬性,serializable和nonserializable屬性將會(huì)被忽略,但是使用xmlignore屬性,和nonserializable屬性類似。
2、該類不能安全地訪問(wèn)私有變成員,所以學(xué)要將私有成員改為公共成員,或者提供合適的公共特性。
3、要求被序列化的類要有一個(gè)默認(rèn)的構(gòu)造器。
我們改一下前面的myobject類為:
public class myobject {
 public int n1;
 public string str;
 public myobject(){}
 public myobject(n1,str)
 {
 this.n1=n1;
 this.str=str;
 }
 public override string tostring()
 {
 return string.format("{0}:{1}",this.str,this.n1);
 }
}
現(xiàn)在我們用xmlserializer類來(lái)對(duì)修改后的myobject進(jìn)行序列化。因?yàn)閤mlserializer類的構(gòu)造器里有個(gè)type參數(shù),所以xmlserializer對(duì)象被明確的 連到該type參數(shù)所表示的類了。xmlserializer類也有serialize和deserialize方法:
myobject obj = new myobject(12,"some string...");
xmlserializer formatter = new xmlserializer(typeof(myobject));
stream stream = new filestream("myfile.xml", filemode.create, 
fileaccess.write, fileshare.none);
formatter.serialize(stream, obj);
//下面是反序列化
stream.seek(0,seekorigin.begin)
myobject obj_out=(myobject)formatter.deserialize(stream)
stream.close();
console.writeline(obj_out);
這個(gè)簡(jiǎn)單的列子可以加以擴(kuò)展,以便利用更多的xmlserializer功能,包括使用屬性控制xml標(biāo)記、使用xml模式和進(jìn)行soap編碼。
自定義序列化
如果你希望讓用戶對(duì)類實(shí)現(xiàn)序列化,但是對(duì)數(shù)據(jù)流的組織方式不完全滿意,那么可以通過(guò)在對(duì)象上實(shí)現(xiàn) iserializable 接口來(lái)自定義序列化過(guò)程。這一功能在反序列化后成員變量的值失效時(shí)尤其有用,但是需要為變量提供值以重建對(duì)象的完整狀態(tài)。除了必須將類申明為 serializable 的同時(shí),還要要實(shí)現(xiàn) iserializable接口,需要實(shí)現(xiàn) getobjectdata 方法以及一個(gè)特殊的構(gòu)造函數(shù),在反序列化對(duì)象時(shí)要用到此構(gòu)造函數(shù)。在實(shí)現(xiàn) getobjectdata 方法時(shí),最常調(diào)用的serializationinfo的方法是addvalue,這個(gè)方法具有針對(duì)所有標(biāo)準(zhǔn)類型(int、char等等)的重載版本;而 streamingcontext 參數(shù)描述給定的序列化流的源和目標(biāo),這樣我們就可以知道我們是將對(duì)象序列化到持久性存儲(chǔ)還是在將他們跨進(jìn)程或機(jī)器序列化。而在反序列化時(shí),我們調(diào)用serializationinfo提供的一組getxxx方法,他們針對(duì)所有標(biāo)準(zhǔn)類型數(shù)據(jù)執(zhí)行各種addvalue重載版本的逆操作。下代碼示例說(shuō)明了如何在前一部分中提到的 myobject 類上實(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ì)象。只需按名稱/值對(duì)的形式添加將要序列化的變量。其名稱可以是任何文本。只要已序列化的數(shù)據(jù)足以在反序列化過(guò)程中還原對(duì)象,便可以自由選擇添加至 serializationinfo 的成員變量。如果基對(duì)象實(shí)現(xiàn)了 iserializable,則派生類應(yīng)調(diào)用其基對(duì)象的 getobjectdata 方法。 
需要強(qiáng)調(diào)的是,將 iserializable 添加至某個(gè)類時(shí),需要同時(shí)實(shí)現(xiàn) getobjectdata 以及特殊的具有特定原型的構(gòu)造函數(shù)--重要的是,該構(gòu)造函數(shù)的參數(shù)列表必須與getobjectdata相同,這個(gè)構(gòu)造函數(shù)將會(huì)在反序列化的過(guò)程中使用:格式化器從流中反序列化數(shù)據(jù),然后通過(guò)這個(gè)構(gòu)造函數(shù)對(duì)對(duì)象進(jìn)行實(shí)列化。如果缺少 getobjectdata,編譯器將發(fā)出警告。但是,由于無(wú)法強(qiáng)制實(shí)現(xiàn)構(gòu)造函數(shù),所以,缺少構(gòu)造函數(shù)時(shí)不會(huì)發(fā)出警告。如果在沒(méi)有構(gòu)造函數(shù)的情況下嘗試反序列化某個(gè)類,將會(huì)出現(xiàn)異常。在消除潛在安全性和版本控制問(wèn)題等方面,當(dāng)前設(shè)計(jì)優(yōu)于 setobjectdata 方法。例如,如果將 setobjectdata 方法定義為某個(gè)接口的一部分,則此方法必須是公共方法,這使得用戶不得不編寫代碼來(lái)防止多次調(diào)用 setobjectdata 方法。可以想象,如果某個(gè)對(duì)象正在執(zhí)行某些操作,而某個(gè)惡意應(yīng)用程序卻調(diào)用此對(duì)象的 setobjectdata 方法,將會(huì)引起一些潛在的麻煩。
在反序列化過(guò)程中,使用出于此目的而提供的構(gòu)造函數(shù)將 serializationinfo 傳遞給類。對(duì)象反序列化時(shí),對(duì)構(gòu)造函數(shù)的任何可見(jiàn)性約束都將被忽略,因此,可以將類標(biāo)記為 public、protected、internal 或 private。一個(gè)不錯(cuò)的辦法是,在類未封裝的情況下,將構(gòu)造函數(shù)標(biāo)記為 protect。如果類已封裝,則應(yīng)標(biāo)記為 private。要還原對(duì)象的狀態(tài),只需使用序列化時(shí)采用的名稱,從 serializationinfo 中檢索變量的值。如果基類實(shí)現(xiàn)了 iserializable,則應(yīng)調(diào)用基類的構(gòu)造函數(shù),以使基礎(chǔ)對(duì)象可以還原其變量。
如果從實(shí)現(xiàn)了 iserializable 的類派生出一個(gè)新的類,則只要新的類中含有任何需要序列化的變量,就必須同時(shí)實(shí)現(xiàn)構(gòu)造函數(shù)以及 getobjectdata 方法。以下代碼片段顯示了如何使用上文所示的 myobject 類來(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)用基類,否則,將永遠(yuǎn)不會(huì)調(diào)用基類上的構(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)行反序列化的類實(shí)現(xiàn)了 ideserializationcallback,則反序列化整個(gè)對(duì)象圖表后,將自動(dòng)調(diào)用 onserialization 方法。此時(shí),引用的所有子對(duì)象均已完全還原。有些類不使用上述事件偵聽(tīng)器,很難對(duì)它們進(jìn)行反序列化,散列表便是一個(gè)典型的例子。在反序列化過(guò)程中檢索關(guān)鍵字/值對(duì)非常容易,但是,由于無(wú)法保證從散列表派生出的類已反序列化,所以把這些對(duì)象添加回散列表時(shí)會(huì)出現(xiàn)一些問(wèn)題。因此,建議目前不要在散列表上調(diào)用方法。