1. 程式人生 > >C# 序列化

C# 序列化

  程式設計師在編寫應用程式的時候往往要將程式的某些資料儲存在記憶體中,然後將其寫入某個檔案或是將它傳輸到網路中的另一臺計算機上以實現通訊。這個將程式資料轉化成能被儲存並傳輸的格式的過程被稱為"序列化"(Serialization),而它的逆過程則可被稱為"反序列化"(Deserialization)。

  .Net框架對序列化機制具有非常好的支援,它提供了兩個名字空間(namespace):System.Runtime.Serialization和System.Runtime.Serialization.Formatters以完成序列化機制的大部分功能。系列化這項技術可以應用在將程式產生的結果資料儲存到檔案系統中,但是它更主要的應用是在於.Net Remoting和Web服務的實現上。

  序列化機制的實現是依靠格式器(Formatter)而完成的,它是一個從System.Runtime.Serialization.IFormatter繼承下來的類的物件。格式器完成了將程式資料轉化到能被儲存並傳輸的格式的工作,同時也完成了將資料轉化回來的工作。.Net框架為程式設計師提供了兩種型別的格式器,一種通常是應用於桌面型別的應用程式的,它一個是System.Runtime.Serialization.Formatters.Binary.BinaryFormatter類的物件,而另一種則更主要的應用於.Net Remoting和XML Web服務等領域的,它一個是System.Runtime.Serialization.Formatters.Soap.SoapFormatter類的物件。從它們的名稱來看,我們不妨將它們分別稱為二進位制格式器和XML格式器。

  本文將從這兩個格式器入手,先向大家介紹分別用它們如何實現序列化和反序列化,然後比較兩種格式器的不同點。接著我會向大家介紹實現序列化對物件型別的一些要求,同時還要向大家介紹兩種不同的序列化方式:基本序列化(Basic Serialization)和自定義序列化(Custom Serialization)。最後,我還會給大家介紹一個例項程式以加深大家對序列化機制的理解程度。

  一.二進位制格式器(Binary Formatter) vs XML格式器(XML Formatter)

  下面我先向大家介紹兩種不同的格式器,分別用它們如何實現序列化機制和反序列化機制,請看下面的程式碼:

#region Binary Serializers

public static System.IO.MemoryStream SerializeBinary(object request) {

 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serializer =

  new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

 System.IO.MemoryStream memStream = new System.IO.MemoryStream();

 serializer.Serialize(memStream, request);

 return memStream;

}

public static object DeSerializeBinary(System.IO.MemoryStream memStream) {

 memStream.Position=0;

 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter deserializer =

  new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

 object newobj = deserializer.Deserialize(memStream);

 memStream.Close();

 return newobj;

}

#endregion

#region XML Serializers

public static System.IO.MemoryStream SerializeSOAP(object request) {

 System.Runtime.Serialization.Formatters.Soap.SoapFormatter serializer =

  new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();

 System.IO.MemoryStream memStream = new System.IO.MemoryStream();

 serializer.Serialize(memStream, request);

 return memStream;

}

public static object DeSerializeSOAP(System.IO.MemoryStream memStream) {

 object sr;

 System.Runtime.Serialization.Formatters.Soap.SoapFormatter deserializer =

  new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();

 memStream.Position=0;

 sr = deserializer.Deserialize(memStream);

 memStream.Close();

 return sr;

}

#endregion

  從上面的程式碼我們可以發現無論運用哪種格式器,其基本的過程都是一樣的,而且都是非常容易實現的,唯一的不同就是定義格式器的型別不同。不過在實際的應用中,二進位制格式器往往應用於一般的桌面程式和網路通訊程式中,而XML格式器稟承了XML技術的優點,大多數被應用於.Net Remoting和XML Web服務等領域。下面我們來分析一下兩種格式器各自的優點。

 

  二進位制序列化的優點:

  1. 所有的類成員(包括只讀的)都可以被序列化;

  2. 效能非常好。

  XML序列化的優點:

  1. 互操作性好;

  2. 不需要嚴格的二進位制依賴;

  3. 可讀性強。

  通過分析上面的程式碼,我們知道了選擇二進位制序列化的方式還是選擇XML序列化的方式僅僅是對不同的格式器進行選擇而已。你可以根據實際的需要選擇相應的格式器完成序列化和反序列化工作。同時請注意,程式碼中的序列化函式和反序列化函式僅僅是在呼叫Serialize()和Deserialize()這兩個核心函式上產生了差別,即它們的引數不同。因此以上的程式碼完成了一些最最基本但是很重要的功能,你可以將它們運用在你的程式中,或是將其進行適當擴充以滿足程式的特定需要。

  二.序列化機制對類的要求:

  如果你要對一個物件進行序列化,那麼你必須將它的型別標記為[Serializable()],該操作是通過SerializableAttribute屬性來實現的。將SerializableAttribute屬性應用於一種資料型別可表明該資料型別的例項可以被序列化。如果正在序列化的物件圖中的任何型別未應用SerializableAttribute屬性,公共語言執行庫則會引發SerializationException。預設情況下,型別中由SerializableAttribute標記的所有公共和私有欄位都會進行序列化,除非該型別實現ISerializable介面來重寫序列化程序(通過實現該介面我們便可以實現將在後面介紹的"自定義序列化")。預設的序列化程序會排除用NonSerializedAttribute屬性標記的欄位,即你可以將該型別標記為[NonSerialized()]以表明它是不可以被序列化的。如果可序列化型別的欄位包含指標、控制代碼或其他某些針對於特定環境的資料結構,並且不能在不同的環境中以有意義的方式重建,則最好將NonSerializedAttribute屬性應用於該欄位。有關序列化的更多資訊,請參閱System.Runtime.Serialization名字空間中的相關內容。

  下面我給大家介紹一個例子,以顯示如何正確的運用SerializableAttribute屬性和NonSerializedAttribute屬性。該程式中運用到了XML格式器,不過同時給出了二進位制格式器為參考(程式中將其用"//"標註),其實現的結果是一樣的。該程式實現的功能是在序列化和反序列化操作前後測試物件因包含了[NonSerialized()]的欄位而顯示不同的螢幕列印結果。其程式碼如下:

using System;

using System.IO;

using System.Runtime.Serialization;

using System.Runtime.Serialization.Formatters.Soap;

//using System.Runtime.Serialization.Formatters.Binary;

public class Test {

 public static void Main() {

  // 建立一個新的測試物件

  TestSimpleObject obj = new TestSimpleObject();

 

  Console.WriteLine("Before serialization the object contains: ");

  obj.Print();

  // 建立一個檔案"data.xml"並將物件序列化後儲存在其中

  Stream stream = File.Open("data.xml", FileMode.Create);

  SoapFormatter formatter = new SoapFormatter();

  //BinaryFormatter formatter = new BinaryFormatter();

 

  formatter.Serialize(stream, obj);

  stream.Close();

  

  // 將物件置空

  obj = null;

 

  // 開啟檔案"data.xml"並進行反序列化得到物件

  stream = File.Open("data.xml", FileMode.Open);

  formatter = new SoapFormatter();

  //formatter = new BinaryFormatter();

 

  obj = (TestSimpleObject)formatter.Deserialize(stream);

  stream.Close();

 

  Console.WriteLine("");

  Console.WriteLine("After deserialization the object contains: ");

  obj.Print();

 }

}

// 一個要被序列化的測試物件的類

[Serializable()]

 public class TestSimpleObject {

 public int member1;

 public string member2;

 public string member3;

 public double member4;

 // 標記該欄位為不可被序列化的

[NonSerialized()] public string member5;

 

public TestSimpleObject() {

 member1 = 11;

 member2 = "hello";

 member3 = "hello";

 member4 = 3.14159265;

 member5 = "hello world!";

}

public void Print() {

 Console.WriteLine("member1 = '{0}'", member1);

 Console.WriteLine("member2 = '{0}'", member2);

 Console.WriteLine("member3 = '{0}'", member3);

 Console.WriteLine("member4 = '{0}'", member4);

 Console.WriteLine("member5 = '{0}'", member5);

}

}

   三.基本序列化(Basic Serialization) vs 自定義序列化(Custom Serialization):

  .Net框架為我們提供了兩種方式的序列化:一種為基本序列化、另一種為自定義序列化。值得注意的是,序列化的方式和前面提到的序列化的格式是不同的概念。序列化的方式是指.Net框架將程式的資料轉化為能被儲存並傳輸的格式的實際過程,它是不管程式設計師運用了何種型別的格式器的(二進位制格式器還是XML格式器)。而序列化的格式則指程式的資料是被轉化成二進位制格式了還是被轉化成XML格式了。

  完成序列化的最簡單的方法便是讓.Net框架自動為我們完成整個過程,而我們不必去管它內部是如何具體實現的,這種方法便是前面提到的"基本序列化"。在這種方式下,我們需要做的僅僅是將類標記上[Serializable()]屬性。然後.Net框架便呼叫該類的物件並將它轉化為所需的格式。同時你還可以控制其中的某些欄位不被序列化,方法就是前面所述的將該欄位標記上[NonSerialized()]屬性。這樣,最最簡單和基本的序列化工作就完成了,不過其內部是如何實現的你是不得而知的,同時你也不能進一步控制序列化過程的程式行為。

  如果你要獲得對序列化的更大的控制權,那麼你就得使用"自定義序列化"的方式。通過使用這種方式,你可以完全的控制類的哪些部分能被序列化而哪些部分不能,同時你還可以控制如何具體的進行序列化。運用該方式的好處就是能克服基本序列化所會遇到的問題。我們在運用基本序列化將一個類的物件序列化完畢並存儲在檔案中後,假設該物件原來有三個欄位,如果此時該物件增加了一個欄位,那麼再將該物件從檔案中反序列化出來時會發生欄位數不一致的錯誤。這樣的問題是基本序列化所不能解決的,只能運用自定義序列化的方式來解決。

  在介紹自定義序列化之前,我先給出介紹過程中所要用到的例項程式的程式碼。這是一個時間安排程式,其中要用到將不同的時間格式進行轉化的操作。所以運用序列化的機制能很好的解決這個問題。

using System;

using System.Runtime.Serialization;

namespace SerializationSample {

[Serializable()]

 public class Schedule {

  protected System.DateTime start;

  protected System.DateTime end;

  // 每個時間間隔所要增加的毫秒數

  protected long interval;

  public System.DateTime Start {get{return start;}set{start=value;}}

  public System.DateTime End {get{return end;}set{end=value;}}

  public long Interval {get{return interval;}set{interval=value;}}

  public Schedule(System.DateTime Start, System.DateTime End, long Interval) {

   start=Start;

   end=End;

  interval=Interval;

}

// 如果已經到了結束的時間,則返回結束時間,否則返回下一次執行的時間

public System.DateTime NextRunTime {

 get {

  System.TimeSpan ts = new System.TimeSpan(end.Ticks-System.DateTime.Now.Ticks);

  if(ts.Milliseconds>0) {

   return System.DateTime.Now.AddMilliseconds(interval);

  } else {

   return end;

  }

 }

}

}

}

  自定義序列化:

  下面我就向大家介紹自定義序列化以及反序列化的具體過程。首先,程式的類必須實現System.Runtime.Serialization.ISerializable介面,該介面的功能就是允許物件控制其自己的序列化和反序列化過程。所以我們得重新定義上面的類:

[Serializable()]

public class ScheduleCustom : System.Runtime.Serialization.Iserializable

  接下來,我們必須對該介面呼叫GetObjectData()的實現,也即我們必須在上面的類中給出GetObjectData()的具體實現。其函式原型如下:

void GetObjectData(SerializationInfo info, StreamingContext context);

  上面的類中GetObjectData()的具體實現如下:

public void GetObjectData(SerializationInfo info,StreamingContext context) {

// 運用info物件來新增你所需要序列化的項

info.AddValue("start", start);

info.AddValue("end", end);

info.AddValue("interval", interval);

}

   然而對於這麼一個簡單的方法,讀者可能不能理會到系列化帶來的強大功能,所以下面我就給這個方法新增一些東西。如果在系列化過程中我們要檢視型別為DateTime的"start"屬性的輸出的話,其結果會是.Net框架預設的格式:

  2002-12-19T14:09:13.3457440-07:00

  而對於沒有.Net框架的使用者,或是在其他時間區域內的使用者而言,這麼一個格式的時間可能是非常難以理解的,所以我們有必要將時間的格式轉化為格林威治標準時間格式,於是修改GetObjectData()方法如下:

public void GetObjectData(SerializationInfo info,StreamingContext context) {

// 運用info物件來新增你所需要序列化的項

// 同時,將"start"和"end"屬性的時間格式轉化為格林威治標準時間格式

info.AddValue("start", System.TimeZone.CurrentTimeZone.ToUniversalTime(start));

info.AddValue("end", System.TimeZone.CurrentTimeZone.ToUniversalTime(end));

info.AddValue("interval", interval);

info.AddValue("timeformat", "utc");

}

  這樣一來,我們在系列化過程中檢視"start"屬性時就會得到如下結果:

   8/19/2002 9:09:13 PM

  同時請注意我們在GetObjectData()方法中新增的一個名為"timeformat"的額外屬性,通過它我們可以方便的知道系列化過程中所使用的時間格式。如果有興趣的話,你還可以從System.Globalization.DateTimeFormatInfo這個名字空間中獲取更多有關時間格式的資訊。

  自定義反序列化:

  你可以通過呼叫一個自定義的建構函式來完成自定義反序列化的操作。該建構函式的定義如下

public ScheduleCustom (SerializationInfo info,StreamingContext context);

  在上面的類中,我們的ScheduleCustom()方法將完成把時間格式從格林威治標準時間格式反序列化為當地時間的格式的操作,其函式實現如下:

public ScheduleCustom (SerializationInfo info,StreamingContext context) {

 this.start = info.GetDateTime("start").ToLocalTime();

 this.end = info.GetDateTime("end").ToLocalTime();

 this.interval = info.GetInt32("interval");

}

  在完成自定義序列化和自定義反序列化後,我們的時間安排程式變成了如下形式:

using System;

using System.Runtime.Serialization;

namespace SerializationSample {

[Serializable()]

 public class ScheduleCustom : System.Runtime.Serialization.ISerializable {

 protected System.DateTime start;

 protected System.DateTime end;

 // 每個時間間隔所要增加的毫秒數

 protected long interval;

 public System.DateTime Start {get{return start;}set{start=value;}}

 public System.DateTime End {get{return end;}set{end=value;}}

 public long Interval {get{return interval;}set{interval=value;}}

 public ScheduleCustom(System.DateTime Start, System.DateTime End, long Interval) {

  start=Start;

  end=End;

  interval=Interval;

 }

// 如果已經到了結束的時間,則返回結束時間,否則返回下一次執行的時間

public System.DateTime NextRunTime {

 get {

  System.TimeSpan ts = new System.TimeSpan(end.Ticks-System.DateTime.Now.Ticks);

  if(ts.Milliseconds>0) {

   return System.DateTime.Now.AddMilliseconds(interval);

  } else {

   return end;

  }

 }

}

public void GetObjectData(SerializationInfo info,StreamingContext context) {

 // 運用info物件來新增你所需要序列化的項

 // 同時,將"start"和"end"屬性的時間格式轉化為格林威治標準時間格式

 info.AddValue("start", System.TimeZone.CurrentTimeZone.ToUniversalTime(start));

 info.AddValue("end", System.TimeZone.CurrentTimeZone.ToUniversalTime(end));

 info.AddValue("interval", interval);

 info.AddValue("timeformat", "utc");

}

public ScheduleCustom (SerializationInfo info,StreamingContext context) {

 this.start = info.GetDateTime("start").ToLocalTime();

 this.end = info.GetDateTime("end").ToLocalTime();

 this.interval = info.GetInt32("interval");

 }

}

}

  四.總結:

  本文向大家介紹了.Net框架下系列化機制的一些基本概念和基本的運用方法,讀者在讀完本文後,應該對以下幾個概念有個初步瞭解:二進位制系列化、XML系列化、基本序列化和自定義系列化,並應能夠完成一些基本的系列化應用。最後,希望大家能合理有效的運用系列化機制併發揮它的功效以更好地滿足實際工作需要。