天天看點

Serialization

Serialization的概念

  Serialization是.NET中一種實作對象持久性(Persistent)的機制。它是一個将對象中的資料轉換成一個單一進制素(通常是Stream)的過程。它的逆過程是Deserialization。Serialization的核心概念是将一個對象的所有資料看作一個獨立的單元。

  一般說來,在兩種情況下非常需要Serialization:1)當我們希望能夠将對象目前的狀态完整地儲存到存儲媒體中,以便我們以後能夠精确地還原對象時;2)當我們希望将對象從一個應用程式空間(Application domain)傳遞到另一個應用程式空間時。例如,Windows Form程式就是利用Serialization機制來實作剪貼闆的copy & paste的。

  .NET Framework支援兩種類型的Serialization:Shallow Serialization和Deep Serialization。

  所謂Shallow Serialization是将對象的可讀寫(read-write)屬性的值轉換成位元組流,而對象内部的資料(沒有通過read-write屬性暴露出來的資料)則不被轉換。XmlSerializer以及Web Services就使用這種技術。

  Deep Serialization比Shallow Serialization更加徹底,因為它是将存儲在對象私有變量裡的實際值拷貝到位元組流裡。而且Deep Serialization還将serialize整個object graph。也就是說,如果你的對象持有其他對象的引用,或者其他對象引用的集合,那麼所有這些對象都将被Serialize。BinaryFormatter和SoapFormatter以及.NET Remoting都使用Deep Serialization技術,它甚至被有限地用于LosFormatter來産生存儲在Web Form頁中的狀态資料。

  本文将着重于Deep Serialization。

  Serialization的過程

  .NET Framework通過Reflection提供自動Serialization的機制。當一個對象被序列化(Serialized)的時候,它的類名,Assembly,以及類執行個體的所有資料成員都将被寫入存儲媒體中。Serialization引擎保持對所有已經被序列化的對象引用的追蹤,以確定相同的對象引用最多隻被序列化一次。

  通常,一個Serialization過程會由formatter(例如BinaryFormatter)的Serialize方法引發。對象的Serialization過程按照以下規則進行:

  1、 檢測以確定formatter是否擁有一個代理選擇器(surrogate selector)。如果有,檢查代理選擇器是否持有給定的對象類型。如果有,ISerializable.GetObjectData被調用。

  2、 如果formatter沒有代理選擇器,或者代理選擇器沒有對象類型,檢查對象是否被用Serializable屬性标記。如果沒有,則抛出SerializationException異常。

  3、 如果對象被标記為Serializable,檢查對象是否實作了ISerializable接口。如果實作了此接口,則GetObjectData被調用。

  4、 如果對象沒有實作ISerializable接口,則使用預設的序列化政策,來序列化沒有用NonSerialized屬性标記的域。

  使你的class能夠被序列化

  通過上面對Serialization過程的分析,我們可以看出,有兩種方式可以使一個class能夠被序列化:1)将此class簡單地标記為Serializable;2)為此class實作ISerializable接口,并将此class标記為Serializable。

  1、 标記Serializable屬性

  标記Serializable屬性的方式是實作Serialization的基本方法。舉個簡單的例子:

   [Serializable]

   public class Person

   {

   public string name = null;

   public int age = 0;

   }

  你可以使用BinaryFormatter來将上面的class序列化:

   Person sam = new Person();

   sam.name = "sam";

   sam.age = 24;

   IFormatter formatter = new BinaryFormatter();

   Stream stream = new FileStream("sam.dat",

   FileMode.Create, FileAccess.Write, FileShare.None);

   formatter.Serialize(stream, sam);

   stream.Close();

  就是這麼簡單,你所要做的就是建立一個Stream和一個formatter的執行個體,然後調用formatter的Serialize方法。經過BinaryFormatter serialize的資料仍然能夠通過BinaryFormatter deserialize回來,方法與serialize同樣簡單,這裡就不贅述了。

  如果你不想将類裡的所有域都序列化,可以使用NonSerialized屬性進行選擇。如:

   [Serializable]

   public class Person

   {

   public string name = null;

   [NonSerialized]

   public int age = 0;

   }

  這樣,age域就不會被序列化了。

  需要注意的是,Serializable屬性并不能被繼承。也就是說如果你希望Person的派生類也能夠被Serialize的話,那麼這個派生類也必須被Serializable标記。否則将得到SerializationException異常。

  同樣的,Person類中的所有對其他類的引用,其所引用的類都應該是能夠被Serialize的。.NET Framework中的大部分class都實作了ISerializable接口,但有些class沒有實作,例如ImageList。可以通過MSDN Library的到一個實作了ISerializable接口的class清單。對那些沒有實作此接口的class,使用的時候要當心。

  2、 實作ISerializable接口

  Serializable屬性的功能非常強大,它使得Serialize和Deserialize變得十分簡單。但凡事有利必有弊,由Serializable實作的自動序列化方法有時不夠靈活。我們并不能完全控制Serialize和Deserialize的行為,而有些時候它們的行為對我們來說很重要。那麼我們通過何種方法能夠控制Serialize和Deserialize的行為呢?答案就是,自己來實作ISerializable接口。ISerializable接口給予我們更大的自由來控制Serialize和Deserialize,但是無疑我們将不得不寫更多的代碼L。

  下面我們來看看如何實作ISerializabe接口。ISerializable接口位于System.Runtime.Serialization名字空間中,聲明如下:

   public inferface ISerializable

   {

   void GetObjectData(SerializationInfo info,

   StreamingContext context);

   }

  它隻有一個方法GetObjectData。是以,像實作其他接口一樣,我們必須實作此方法。但與其他接口不同的是,為了Deserialization,我們還必須實作一個特殊的構造函數(我稱此構造函數為“序列化構造函數”),此構造函數具有與GetObjectData相同的參數清單。由于此構造函數專門用于.NET Framework在Deserialize時的Reflection機制,是以我們通常将它聲明為保護或私有模式。如下:(當然,如果你的class隻需要Serialize而不需要Deserialize的話,也可以不實作這個特殊的構造函數)

   [Serializable]

   public class Person : ISerializable

   {

   public string name = null;

   public int age = 0;

   public Person()

   {

   }

   protected Person(SerializationInfo info, StreamingContext context)

   {

   name = info.GetString("name");

   age = info.GetInt32("age");

   }

   void ISerializable.GetObjectData(SerializationInfo info,

   StreamingContext context)

   {

   info.AddValue("name", name);

   info.AddValue("age", age);

   }

   }

  通過實作ISerializable接口,使得我們有機會在ISerializable.GetObjectData中控制Serialize的行為,在“序列化構造函數”中控制Deserialize的行為。這個接口提供給我們的資訊非常全面而靈活,以緻于我們甚至可以在這兩個方法中耍些花招。比如,我們可以在Deserialize的時候,籍由改變info.FullTypeName來得到一種與被Serialize的對象不同類型的另一個對象等。

  獨辟蹊徑

  前面談到過Serialization被運用的典型環境,是對象存儲、程序間資料傳遞等涉及到對象持久性的領域。但實際上,它也能夠被運用到其他的許多地方,關鍵在于我們是否能想到去用運Serialization,有時候思維定式也是很可怕的J。舉個例子,我們來看看在Clone方法中如何使用Serialization[1]。

  如果我們要為Person類實作Clone方法,我們通常會這樣寫:

   [Serializable]

   public class Person : ICloneable

   {

   public string name = null;

   public int age = 0;

   public object Clone()

   {

   Person person = new Person();

   person.name = name;

   person.age = age;

   return person;

   }

   }

  如果我們利用Serialization的方法,Clone函數就能寫成下面的樣子:

   public object Clone() 

{

   MemoryStream stream = new MemoryStream();

   BinaryFormatter formatter = new BinaryFormatter();

   formatter.Serialize(stream, this);

   stream.Position = 0;

   return formatter.Deserialize(stream);

   }

  從這兩個實作上看,使用Serialization實作Clone方法似乎并沒有什麼好處。可是設想如果你面對的是一個複雜的類繼承體系,從基類到派生類都需要實作Clone方法。利用第一種實作手法,你将不得不為每一個class寫一個Clone方法,而且随着資料成員的增多,這個方法将越來越冗長,并且會由于資料成員的改變而引發錯誤(我曾經遇到過好幾次,由于class中增加了成員變量,而Clone方法沒有及時更新,導緻運作時錯誤。呵呵,這種錯誤還很難調試)。現在你看到用Serialization實作的好處了吧?是的,我們隻要在基類中将Clone方法聲明為virtual,并用Serialization的方法實作之,然後保證基類和派生類都可以被Serialize,上面所有的麻煩不都迎刃而解了嗎?

  總結

  現代軟體項目中,無論何種項目都會或多或少地涉及到對象持久性的問題,.NET也不例外,無論是Windows Form、ASP.NET,還是Web Services,都需要處理對象持久性。而Serialization正是.NET為應對這個問題而給出的解法。