程序員在編寫應用程序的時候往往要將程序的某些數據存儲在內存中,然后將其寫入某個文件或是將它傳輸到網絡中的另一台計算機上以實現通訊。這個將程序數據轉化成能被存儲並傳輸的格式的過程被稱為"序列化"(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系列化、基本序列化和自定義系列化,並應能夠完成一些基本的系列化應用。最后,希望大家能合理有效的運用系列化機制並發揮它的功效以更好地滿足實際工作需要。