程式員在編寫應用程式的時候往往要將程式的某些資料存放區在記憶體中,然後將其寫入某個檔案或是將它傳輸到網路中的另一台電腦上以實現通訊。這個將程式資料轉化成能被儲存並傳輸的格式的過程被稱為"序列化"(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系列化、基本序列化和自訂系列化,並應能夠完成一些基本的系列化應用。最後,希望大家能合理有效運用系列化機制並發揮它的功效以更好地滿足實際工作需要。